mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-16 14:16:09 -06:00
519 lines
12 KiB
C++
519 lines
12 KiB
C++
#include "RichText.h"
|
|
|
|
namespace Upp {
|
|
|
|
Index<String> DiagramItem::LineCap = { "none", "arrow", "circle", "disc", "dim", "T",
|
|
"arrowL", "circleL", "discL", "dimL", "TL" };
|
|
|
|
Index<String> DiagramItem::Shape = { "line", "rect", "round_rect",
|
|
"ellipse", "diamond", "oval", "parallelogram",
|
|
"cylinder", "triangle",
|
|
"arrow_right", "arrow_horz", "arrow_down", "arrow_vert",
|
|
"arc",
|
|
"svgpath", "image"
|
|
};
|
|
|
|
Vector<Pointf> DiagramItem::GetConnections() const
|
|
{
|
|
Vector<Pointf> 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<double>& DiagramItem::GetDash(int i)
|
|
{
|
|
static Vector<double> 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<Pointf> *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<double> 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<String, String&>(), paper);
|
|
}
|
|
else {
|
|
StringStream ss(s);
|
|
Value v = MakeValue(
|
|
[&] {
|
|
return blob_id;
|
|
},
|
|
[&](Value& v) {
|
|
v = Null;
|
|
One<StreamRaster> r = StreamRaster::OpenAny(ss);
|
|
if(r) {
|
|
Image m = r->GetImage();
|
|
v = m;
|
|
return int(m.GetLength() * sizeof(RGBA));
|
|
}
|
|
return 0;
|
|
}
|
|
);
|
|
if(v.Is<Image>()) {
|
|
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<StreamRaster> r = StreamRaster::OpenAny(ss);
|
|
if(r) {
|
|
v = r->GetSize();
|
|
return 32;
|
|
}
|
|
return 0;
|
|
}
|
|
);
|
|
if(v.Is<Size>())
|
|
return v.To<Size>();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
}
|