#include "RichText.h" namespace Upp { Index DiagramItem::LineCap = { "none", "arrow", "circle", "disc", "dim", "T", "arrowL", "circleL", "discL", "dimL", "TL" }; Index DiagramItem::Shape = { "line", "rect", "round_rect", "ellipse", "diamond", "oval", "parallelogram", "cylinder", "triangle", "arrow_right", "arrow_horz", "arrow_down", "arrow_vert", "arc", "svgpath", "image" }; Vector DiagramItem::GetConnections() const { Vector p; if(shape > SHAPE_TRIANGLE) return p; if(IsLine()) { p << pos << pos + size; return p; } Rectf r = GetRect(); p << r.TopCenter() << r.BottomCenter(); if(findarg(shape, SHAPE_PARALLELOGRAM, SHAPE_TRIANGLE) < 0) p << r.CenterLeft() << r.CenterRight(); if(shape == SHAPE_TRIANGLE) p << r.BottomLeft() << r.BottomRight(); if(rotate) { Xform2D rot = Rotation(); Pointf c = r.CenterPoint(); for(Pointf& x : p) { x -= c; x = rot.Transform(x); x += c; } } return p; } const Vector& DiagramItem::GetDash(int i) { static Vector dashes[10] = { { 0 }, { 1, 1 }, { 2 }, { 1, 2 }, { 2, 1 }, { 2, 1, 1, 1 }, { 3, 1 }, { 1, 3 }, { 1, 4 }, { 2, 5 } }; return dashes[clamp(i, 0, 9)]; } void DiagramItem::Paint(Painter& w, const Diagram& diagram, dword style, const Index *conn) const { bool dark = style & DARK; auto SC = [&](Color c) { return dark ? DarkThemeCached(c) : c; }; Color ink = SC(this->ink); Color paper = SC(this->paper); PaintInfo pi; pi.darktheme = dark; pi.zoom = Diagram::TextZoom(); RichText txt = ParseQTF(qtf); auto DoDash = [&] { if(dash) { Vector d = clone(GetDash(dash)); for(double& h : d) h *= width; w.Dash(d, 0); } }; RGBA sel1 = 150 * SColorHighlight(); RGBA sel2 = 150 * Gray(); double stroke = width; auto Stroke = [&] { if(stroke) w.Stroke(stroke, ink); else if(style & GRID) w.Stroke(0.2, sel1); }; w.Move(0, 0).EndPath(); // this is to start a new path for every item if(IsLine()) { if(style) { w.Move(pos).RelLine(size).EndPath(); w.Begin(); if((style & EDITOR) && width == 0) w.Dash("5 1").Stroke(1, 100 * sel2); if(style & (Display::CURSOR | Display::SELECT)) { w.LineCap(LINECAP_ROUND).Stroke(width + 12, (style & Display::SELECT ? 30 : 200) * sel2); double r = (width + 12) / 2 - 1; w.Circle(pos, r).Fill(sel1); w.Circle(pos + size, r).Fill(sel1); } w.End(); } Pointf v = size; double d = Length(v); v = Upp::Normalize(v); Pointf a1 = pos; Pointf a2 = pos + size; auto CapDef = [&](int i, double& reserve, double& reduce) { reserve = 0; reduce = 0; switch(cap[i]) { case CAP_DIM: case CAP_ARROW: reserve = reduce = 4 * width; break; case CAP_DISC: case CAP_CIRCLE: reserve = 1.5 * width; break; case CAP_DIML: case CAP_ARROWL: reserve = reduce = 12 * width; break; case CAP_DISCL: case CAP_CIRCLEL: reserve = 2.5 * width; break; } }; bool docap[2]; double reserve, reduce; double dd = d; CapDef(0, reserve, reduce); docap[0] = dd > reserve; if(docap[0]) { a1 += v * reduce; dd -= reserve; } CapDef(1, reserve, reduce); docap[1] = dd > reserve; if(docap[1]) a2 -= v * reduce; w.Move(a1).Line(a2); DoDash(); Stroke(); Pointf o = Orthogonal(v); auto PaintCap = [&](int k, Pointf p, Pointf a) { Pointf oo = max(3.0, width * 2) * o; Pointf ool = max(6.0, width * 4) * o; switch(k) { case CAP_NONE: w.Circle(p, width / 2).Fill(ink); break; case CAP_T: w.Move(p - 2 * oo).Line(p + 2 * oo).Stroke(1, ink); break; case CAP_DIM: w.Move(p - 2 * oo).Line(p + 2 * oo).Stroke(1, ink); case CAP_ARROW: w.Move(p).Line(a + oo).Line(a - oo).Fill(ink); break; case CAP_DISC: w.Circle(p, 1.5 * width).Fill(ink); break; case CAP_CIRCLE: w.Circle(p, 1.5 * width).Fill(paper).Stroke(1, ink); break; case CAP_TL: w.Move(p - 2 * ool).Line(p + 2 * ool).Stroke(1, ink); break; case CAP_DIML: w.Move(p - 2 * ool).Line(p + 2 * ool).Stroke(1, ink); case CAP_ARROWL: w.Move(p).Line(a + ool).Line(a - ool).Fill(ink); break; case CAP_DISCL: w.Circle(p, 2.5 * width).Fill(ink); break; case CAP_CIRCLEL: w.Circle(p, 2.5 * width).Fill(paper).Stroke(1, ink); break; } }; if(docap[0]) PaintCap(cap[0], pos, a1 + v); if(docap[1]) PaintCap(cap[1], pos + size, a2 - v); int cx = (int)d; int txt_cy = txt.GetHeight(pi.zoom, cx); w.Begin(); double angle = Bearing(size); if(angle >= -M_PI / 2 && angle <= M_PI / 2) { w.Translate(pos - o * (txt_cy + 10)); w.Rotate(angle); } else { w.Translate(pos + size + o * (txt_cy + 10)); w.Rotate(angle + M_PI); } txt.Paint(w, 0, 0, cx, pi); w.End(); } else { Rectf r = GetRect(); r.Normalize(); r.Deflate(width / 2); double w1 = r.GetWidth(); double h = r.GetHeight(); double cx = r.GetWidth(); double cy = r.GetHeight(); Rectf text_rect = r.Deflated(width + 2, 0).Offseted(-r.TopLeft()); double sz = min(cx, cy); double arrow_width = min(cx / 3, cy / 3); double arrow_height = min(cx / 3, cy / 3); double h2 = h / 2; double h4 = h / 4; double bh4 = h - h4; double w2 = w1 / 2; double w4 = w1 / 4; Pointf m(w2, h2); double hc, thc, bhc; // cylinder w.Begin(); w.Translate(r.left, r.top); if(rotate) { w.Translate(w2, h2); w.Rotate(M_2PI * rotate / 360); w.Translate(-w2, -h2); } if(style & (Display::CURSOR | Display::SELECT)) { w.RoundedRectangle(-2, -2, w1 + 4, h + 4, 5) .Stroke(6, (style & Display::SELECT ? 30 : 200) * sel1); w.RoundedRectangle(-1, -1, w1 + 2, h + 2, 2) .Stroke(2, Gray()); if(style & Display::CURSOR) w.Circle(0, h, 6).Stroke(1, Red()); } w.Begin(); if(flip_horz) { w.Translate(cx, 0); w.Scale(-1, 1); } if(flip_vert) { w.Translate(0, cy); w.Scale(1, -1); } switch(shape) { case SHAPE_ROUNDRECT: w.RoundedRectangle(0, 0, w1, h, sz > 30 ? 8 : sz > 15 ? 4 : 2); break; case SHAPE_OVAL: if(w1 > h) { double ra = h2; w.Move(ra, 0) .Line(w1 - ra, 0) .Arc(w1 - ra, 0 + ra, ra, -M_PI / 2, M_PI) .Line(ra, cy) .Arc(ra, 0 + ra, ra, M_PI / 2, M_PI); break; } case SHAPE_ELLIPSE: w.Ellipse(m.x, m.y, w2, h2); break; case SHAPE_DIAMOND: w.Move(m.x, 0).Line(cx, m.y).Line(m.x, cy).Line(0, m.y).Close(); break; case SHAPE_PARALLELOGRAM: w.Move(cx / 6, 0).Line(cx, 0).Line(5 * cx / 6, cy).Line(0, cy).Close(); break; case SHAPE_CYLINDER: text_rect.top += int(w1 / 4); hc = h / 6; thc = 0 + hc; bhc = cy - hc; w.Move(0, thc) .Arc(0 + w2, thc, w2, hc, M_PI, M_PI) .Line(cx, bhc) .Arc(0 + w2, bhc, w2, hc, 0, M_PI) .Line(0, bhc); break; case SHAPE_TRIANGLE: { text_rect.left += w4; text_rect.right -= w4; text_rect.top += int(cx / 3); w.Move(w2, 0).Line(cx, cy).Line(0, cy).Close(); } break; case SHAPE_ARROWRIGHT: { double a = cx - arrow_width; text_rect.right -= int(arrow_width / 3); w.Move(cx, cy / 2) .Line(a, 0) .Line(a, h4) .Line(0, h4) .Line(0, bh4) .Line(a, bh4) .Line(a, cy) .Close(); } break; case SHAPE_ARROWHORZ: { double a1 = 0 + arrow_width; text_rect.left += int(arrow_width / 3); double a2 = cx - arrow_width; text_rect.right -= int(arrow_width / 3); w.Move(0, h2) .Line(a1, 0) .Line(a1, h4) .Line(a2, h4) .Line(a2, 0) .Line(cx, h2) .Line(a2, cy) .Line(a2, bh4) .Line(a1, bh4) .Line(a1, cy) .Close(); } break; case SHAPE_ARROWDOWN: { double a = cy - arrow_height; text_rect.left += w4; text_rect.right -= w4; text_rect.bottom -= 3 * arrow_height / 4; w.Move(0 + cx / 2, cy) .Line(cx, a) .Line(cx - w4, a) .Line(cx - w4, 0) .Line(w4, 0) .Line(w4, a) .Line(0, a) .Close(); } break; case SHAPE_ARROWVERT: { double a1 = 0 + arrow_height; double a2 = cy - arrow_height; text_rect.left += w4; text_rect.right -= w4; w.Move(cx / 2, 0) .Line(cx, a1) .Line(cx - w4, a1) .Line(cx - w4, a2) .Line(cx, a2) .Line(cx / 2, cy) .Line(0, a2) .Line(w4, a2) .Line(w4, a1) .Line(0, a1) .Close(); } break; case SHAPE_ARC: if(w2 > 0) { double a = (sqr(w2) - sqr(cy)) / (2 * cy); double f = atan(w2 / a); if(a >= 0) w.Move(0, cy).Arc(w2, a + cy, a + cy, -M_PI / 2 - f, 2 * f); else w.Move(0, cy).Arc(w2, cy, w2, cy, M_PI, M_PI); } break; case SHAPE_SVGPATH: { Rectf bb = diagram.GetBlobSvgPathBoundingBox(blob_id); Sizef size = bb.GetSize(); if(size.cx > 0 && size.cy > 0) { w.Scale(w1 / size.cx, h / size.cy); stroke *= min(size.cx / w1, size.cy / h); w.Translate(-bb.TopLeft()); w.Path(diagram.GetBlob(blob_id)); } } break; case SHAPE_IMAGE: { if(style & FAST) { w.Rectangle(0, 0, cx, cy).Fill(Gray()); break; } String s = diagram.GetBlob(blob_id); if(s.GetCount()) { if(IsSVG(s)) { Rectf f = GetSVGBoundingBox(s); w.Scale(cx / f.GetWidth(), cy / f.GetHeight()); w.Translate(-f.left, -f.top); RenderSVG(w, s, Event(), paper); } else { StringStream ss(s); Value v = MakeValue( [&] { return blob_id; }, [&](Value& v) { v = Null; One r = StreamRaster::OpenAny(ss); if(r) { Image m = r->GetImage(); v = m; return int(m.GetLength() * sizeof(RGBA)); } return 0; } ); if(v.Is()) { Image img = v; w.Rectangle(0, 0, cx, cy).Fill(img, Xform2D::Scale(cx / img.GetWidth(), cy / img.GetHeight())); } } } } break; default: w.Rectangle(0, 0, cx, cy); break; } if(shape != SHAPE_IMAGE) { DoDash(); if(shape != SHAPE_ARC) w.Fill(paper); Stroke(); } switch(shape) { case SHAPE_CYLINDER: w.Move(0, thc) .Arc(0 + w2, thc, w2, hc, M_PI, -M_PI); DoDash(); Stroke(); break; } w.End(); Rectf tr2 = text_rect; if(flip_horz) { text_rect.right = cx - tr2.left; text_rect.left = cx - tr2.right; } if(flip_vert) { text_rect.top = cy - tr2.bottom; text_rect.bottom = cy - tr2.top; } Rect tr = text_rect; int txt_cy = txt.GetHeight(pi.zoom, tr.GetWidth()); txt.Paint(w, tr.left, tr.top + (tr.GetHeight() - txt_cy) / 2, tr.GetWidth(), pi); w.End(); if((style & GRID)) for(Pointf p : GetConnections()) { w.Circle(p, 5); if(conn && conn->Find(p) >= 0) w.Fill(128 * SYellow()); w.Stroke(1, 190 * SColorHighlight()); } } } Sizef DiagramItem::GetStdSize(const Diagram& diagram) const { switch(shape) { case SHAPE_SVGPATH: return diagram.GetBlobSvgPathBoundingBox(blob_id).GetSize(); case SHAPE_IMAGE: { String s = diagram.GetBlob(blob_id); if(s.GetCount()) { if(IsSVG(s)) return GetSVGBoundingBox(s).GetSize(); else { StringStream ss(s); Value v = MakeValue( [&] { return blob_id; }, [&](Value& v) { v = Null; One r = StreamRaster::OpenAny(ss); if(r) { v = r->GetSize(); return 32; } return 0; } ); if(v.Is()) return v.To(); } } } } if(shape == SHAPE_CYLINDER) return Size(96, 128); if(findarg(shape, SHAPE_CYLINDER, SHAPE_ARROWDOWN, SHAPE_ARROWVERT) >= 0) return Size(64, 128); return Size(128, 64); } }