#include #pragma hdrstop #include "formula.h" NAMESPACE_UPP #define LLOG(x) // RLOG(x) #define LDUMP(x) // RDUMP(x) #ifdef _DEBUG #define SHOW_BOXES 0 // 1 = on, 0 = off #else #define SHOW_BOXES 0 #endif static Point Bezier(Point A, Point B, Point C, double t) { double s = 1 - t; return Point( fround(A.x * s * s + B.x * 2 * t * s + C.x * t * t), fround(A.y * s * s + B.y * 2 * t * s + C.y * t * t)); } static void Bezier(Vector& out, Point A, Point B, Point C, int n) { if(out.IsEmpty() || out.Top() != A) out.Add(A); for(int i = 0; i <= n; i++) out.Add(Bezier(A, B, C, i / double(n))); if(out.Top() != C) out.Add(C); } static void Bezier(Vector& out, const Vector& in, int n) { for(int i = 2; i < in.GetCount(); i += 2) Bezier(out, in[i - 2], in[i - 1], in[i - 0], n); } static void Rectangle(Vector& out, Rect rc) { out << rc.TopLeft() << rc.TopRight() << rc.BottomRight() << rc.BottomLeft(); } static void BezierEllipse(Vector& out, Point C, Size radius, int n) { Vector temp; temp << Point(C.x + radius.cx, C.y) << Point(C.x + radius.cx, C.y + radius.cy) << Point(C.x, C.y + radius.cy) << Point(C.x - radius.cx, C.y + radius.cy) << Point(C.x - radius.cx, C.y) << Point(C.x - radius.cx, C.y - radius.cy) << Point(C.x, C.y - radius.cy) << Point(C.x + radius.cx, C.y - radius.cy) << Point(C.x + radius.cx, C.y); Bezier(out, temp, n); } int SymbolWidth(FORMSYMBOL symbol, int height, int textheight) { int wd = max(Percent(20, textheight), 4); int lw = Formula::GetLineWidth(textheight); switch(symbol) { default: NEVER(); case FS_EMPTY: return 0; case FS_LBRACE: case FS_RBRACE: case FS_LSHARP: case FS_RSHARP: wd = textheight / 4; break; case FS_LPAREN: case FS_RPAREN: case FS_LBRACKET: case FS_RBRACKET: case FS_LTBRACKET: case FS_RTBRACKET: case FS_LBBRACKET: case FS_RBBRACKET: break; case FS_VBAR: wd = textheight / 6; break; case FS_SUM: case FS_PRODUCT: case FS_UNION: case FS_INTERSECTION: wd = (height * 10) >> 4; break; case FS_INT: wd = lw + height / 8 + 1 * (textheight / 4); break; case FS_IINT: wd = lw + height / 8 + 2 * (textheight / 4); break; case FS_IIINT: wd = lw + height / 8 + 3 * (textheight / 4); break; case FS_OINT: wd = lw + height / 8 + 2 * (textheight / 4); break; case FS_OIINT: wd = lw + height / 8 + 3 * (textheight / 4); break; case FS_LOG_NOT: wd = Percent(40, textheight); break; case FS_LOG_OR: wd = Percent(60, textheight); break; case FS_MEMBER: case FS_NOT_MEMBER: case FS_SUBSET: case FS_NOT_SUBSET: case FS_PROPER_SUBSET: case FS_NOT_PROPER_SUBSET: wd = Percent(60, textheight); break; } return wd; } void PaintSymbol(Draw& draw, FORMSYMBOL symbol, const Rect& rc, int textheight, Color color) { // draw.DrawRect(rc, LtMagenta); int wd = rc.Width(), ht = rc.Height(); int thick = Formula::GetLineWidth(textheight); int half = thick >> 1; Vector polygon; Vector counts; bool xmirror = false; if(symbol > FS_BRACKET_BEGIN && symbol < FS_BRACKET_END) { xmirror = (symbol == FS_RBRACKET || symbol == FS_RTBRACKET || symbol == FS_RBBRACKET); bool top = (symbol != FS_LBBRACKET && symbol != FS_RBBRACKET); bool bot = (symbol != FS_LTBRACKET && symbol != FS_RTBRACKET); polygon << Point(0, 0) << Point(0, ht); if(bot) polygon << Point(wd, ht) << Point(wd, ht - half) << Point(thick, ht - half); else polygon << Point(thick, ht); if(top) polygon << Point(thick, half) << Point(wd, half) << Point(wd, 0); else polygon << Point(thick, 0); } else if(symbol == FS_LPAREN || symbol == FS_RPAREN || symbol == FS_LBRACE || symbol == FS_RBRACE) { bool bra = (symbol == FS_LBRACE || symbol == FS_RBRACE); xmirror = (symbol == FS_RPAREN || symbol == FS_RBRACE); int xo, xv, xs; int yo, yv, yw, yi, yj, ys; xo = wd; xs = xv = thick; yo = 0; yi = yj = ys = ht >> 1; if(bra) { xv = (xv + xo) >> 1; yi = max(ht >> 2, ys - textheight / 5); yj = (yi + ys) >> 1; } yw = min(textheight / 3, yi); yv = yw >> 1; enum { PREC = 6 }; Bezier(polygon, Point(xo, yo), Point(xv, yv), Point(xv, yw), PREC); if(bra) { Bezier(polygon, Point(xv, yi), Point(xv, yj), Point(xs, ys), PREC); Bezier(polygon, Point(xs, ht - ys), Point(xv, ht - yj), Point(xv, ht - yi), PREC); } Bezier(polygon, Point(xv, ht - yw), Point(xv, ht - yv), Point(xo, ht - yo), PREC); xo -= half; xv -= thick; xs -= thick; Bezier(polygon, Point(xo, ht - yo), Point(xv, ht - yv), Point(xv, ht - yw), PREC); if(bra) { Bezier(polygon, Point(xv, ht - yi), Point(xv, ht - yj), Point(xs, ht - ys), PREC); Bezier(polygon, Point(xs, ys), Point(xv, yj), Point(xv, yi), PREC); } Bezier(polygon, Point(xv, yw), Point(xv, yv), Point(xo, yo), PREC); } else if(symbol == FS_LSHARP || symbol == FS_RSHARP) { } else if(symbol == FS_VBAR) { int lw = Formula::GetLineWidth(textheight); draw.DrawRect((rc.left + rc.right - lw) >> 1, rc.top, lw, rc.Height(), color); return; } else if(symbol == FS_SUM) { int tt = (thick * 20) >> 4; int cx = (wd * 8) >> 4; int hh = ht >> 1; int dx = tt + iscale(thick, cx, hh); polygon << Point(wd, 0) << Point(0, 0) << Point(cx, hh) << Point(0, ht) << Point(wd, ht) << Point(wd, ht - thick) << Point(dx, ht - thick) << Point(cx + tt, hh) << Point(dx, thick) << Point(wd, thick); } else if(symbol == FS_PRODUCT) { } else if(symbol == FS_UNION) { } else if(symbol == FS_INTERSECTION) { } else if(symbol > FS_INT_BEGIN && symbol < FS_INT_END) { int count = (symbol == FS_IIINT ? 3 : symbol == FS_IINT || symbol == FS_OIINT ? 2 : 1); int lw = thick; int rcap = min(textheight / 12, (wd - lw) / (2 * count)), dcap = 2 * rcap, vcap = rcap; int capdist = (dcap * 25) >> 4; int dcl = dcap - lw, rcl = dcl >> 1; int slant = wd - capdist * (count - 1); int comp = dcap - iscale(rcap, slant - 2 * dcap + lw, ht - 2 * rcap); for(int i = 0; i < count; i++) { int x = i * capdist; int n = polygon.GetCount(); BezierEllipse(polygon, Point(x + rcap, ht - rcap), Size(rcap, vcap), 4); counts.Add(polygon.GetCount() - n); n = polygon.GetCount(); BezierEllipse(polygon, Point(x + slant - rcap, rcap), Size(rcap, vcap), 4); counts.Add(polygon.GetCount() - n); n = polygon.GetCount(); polygon << Point(x + slant - dcap, rcap) << Point(x + slant - dcl, rcap) << Point(x + dcap, ht - rcap) << Point(x + dcl, ht - rcap); counts.Add(polygon.GetCount() - n); } if(symbol == FS_OINT || symbol == FS_OIINT) { int n = polygon.GetCount(); int rad = min(wd >> 1, count * textheight / 4); BezierEllipse(polygon, Point(wd >> 1, ht >> 1), Size(rad, rad), 8); BezierEllipse(polygon, Point(wd >> 1, ht >> 1), Size(rad - lw, -rad + lw), 8); counts.Add(polygon.GetCount() - n); } } else if(symbol == FS_LOG_NOT) { polygon << Point(0, 0) << Point(wd, 0) << Point(wd, ht) << Point(wd - thick, ht) << Point(wd - thick, thick) << Point(0, thick); } else if(symbol == FS_LOG_OR) { int top = Percent(30, ht); int bot = Percent(80, ht); int wd2 = wd >> 1; polygon << Point(0, top) << Point(wd2, bot) << Point(wd, top) << Point(wd - thick, top) << Point(wd2, bot - thick - iscale(thick, bot - top, wd2)) << Point(thick, top); } else if(symbol > FS_SETOP_BEGIN && symbol < FS_SETOP_END) { int ht2 = ht >> 1, ht4 = Percent(30, ht), wd2 = wd >> 1; polygon << Point(wd, ht2 - ht4); Bezier(polygon, Point(wd2, ht2 - ht4), Point(0, ht2 - ht4), Point(0, ht2), 8); Bezier(polygon, Point(0, ht2), Point(0, ht2 + ht4), Point(wd2, ht2 + ht4), 8); polygon << Point(wd, ht2 + ht4) << Point(wd, ht2 + ht4 - thick); Bezier(polygon, Point(wd2 + half, ht2 + ht4 - thick), Point(thick, ht2 + ht4 - thick), Point(thick, ht2), 8); Bezier(polygon, Point(thick, ht2), Point(thick, ht2 - ht4 + thick), Point(wd2 + half, ht2 - ht4 + thick), 8); polygon << Point(wd, ht2 - ht4 + thick); counts.Add(polygon.GetCount()); if(symbol == FS_MEMBER || symbol == FS_NOT_MEMBER) { Rectangle(polygon, Rect(thick, ht2 - half, wd2, ht2 + half)); counts.Add(4); } if(symbol == FS_SUBSET || symbol == FS_NOT_SUBSET) { int y = ht2 + ht4 + 2 * thick; Rectangle(polygon, RectC(0, y, wd, thick)); counts.Add(4); } if(symbol == FS_NOT_MEMBER || symbol == FS_NOT_SUBSET || symbol == FS_NOT_PROPER_SUBSET) { polygon << Point(wd, 0) << Point(thick, ht) << Point(0, ht) << Point(wd - thick, 0); counts.Add(4); } } else { ASSERT(symbol == FS_EMPTY); return; } if(xmirror) for(int i = 0; i < polygon.GetCount(); i++) polygon[i].x = wd - polygon[i].x; draw.Offset(rc.TopLeft()); if(counts.IsEmpty()) counts.Add(polygon.GetCount()); DrawPolygons(draw, polygon, counts, color, 0, color); draw.End(); } void Formula::PaintRect0(RefCon f, Draw& draw, int x_left, int y_baseline) { if(!!f) f->PaintRect(draw, x_left, y_baseline); } void Formula::PaintRect(Draw& draw, int x_left, int y_baseline) const { #if SHOW_BOXES int wd = DotsToPixels(draw, 20); draw.DrawRect(x_left, y_baseline - ascent, width, wd, Yellow); draw.DrawRect(x_left, y_baseline + descent - wd, width, wd, Yellow); draw.DrawRect(x_left + width - wd, y_baseline - ascent, wd, ascent + descent, Yellow); draw.DrawRect(x_left, y_baseline - (wd >> 1), width, wd, LtMagenta); draw.DrawRect(x_left, y_baseline, width, 1, LtBlue); draw.DrawRect(x_left, y_baseline, wd, descent, Color(0, 192, 0)); draw.DrawRect(x_left, y_baseline - ascent, wd, ascent, LtCyan); #endif Paint(draw, x_left, y_baseline); } int Formula::GetTextDeltaY(Font font) { FontInfo fi = font.Info(); return Percent(fi.GetHeight(), 32) - fi.GetAscent(); } Size GetScaledSize(Size from, Size to) { if(to.cx == 0 || to.cy == 0) return to; if(from.cx == 0 || from.cy == 0) return Size(0, 0); if(from.cx * to.cy < to.cx * from.cy) return Size(iscale(to.cy, from.cx, from.cy), to.cy); else return Size(to.cx, iscale(to.cx, from.cy, from.cx)); } FormulaBox::FormulaBox(RefCon in_, Color color, int flags) : in(in_), color(color), flags(flags) { textheight = in->GetTextHeight(); boxwidth = GetLineWidth(textheight); offset = (flags & INNER ? -boxwidth : flags & TIGHT ? 0 : GetLineOffset(textheight)); if(flags & THICK) boxwidth *= 2; ascent = in->GetAscent() + offset + boxwidth; descent = in->GetDescent() + offset + boxwidth; width = in->GetWidth() + 2 * (offset + boxwidth); } void FormulaBox::Paint(Draw& draw, int x_left, int y_baseline) const { DrawFatFrame(draw, x_left, y_baseline - ascent, width, ascent + descent, color, boxwidth); in->PaintRect(draw, x_left + boxwidth + offset, y_baseline); } FormulaSymbol::FormulaSymbol(FORMSYMBOL symbol_, int height_, int textheight_, Color color_) : symbol(symbol_), height(height_), color(color_) { textheight = textheight_; leading = GetLeading(textheight); ascent = height >> 1; descent = height - ascent; width = SymbolWidth(symbol, height - 2 * leading.cy, textheight) + 2 * leading.cx; } void FormulaSymbol::Paint(Draw& draw, int x_left, int y_baseline) const { PaintSymbol(draw, symbol, Rect(x_left, y_baseline - ascent, x_left + width, y_baseline + descent).Deflated(leading), textheight, color); } FormulaText::FormulaText(WString text, Font font, Color color, int dy) : text(text), font(font), color(color) { width = GetTextSize(text, font).cx; FontInfo fi = font.Info(); int fht = fi.GetHeight(); const wchar *p = text; while(*p && IsLower(*p)) p++; textheight = font.GetHeight(); if(font.IsItalic()) width += GetItalicDelta(fht); ascent = fht >> 1; descent = fht - ascent; dy = Percent(dy, textheight); deltay = GetTextDeltaY(font) + dy; if(*p == 0) ascent -= textheight / 9; ascent -= max(dy, 0); descent += min(dy, 0); LLOG("FormulaText::FormulaText"); LDUMP(text); LDUMP(width); LDUMP(ascent); LDUMP(descent); LDUMP(textheight); LDUMP(deltay); LDUMP(fi.GetAscent()); LDUMP(fi.GetDescent()); LDUMP(fi.GetHeight()); } void FormulaText::Paint(Draw& draw, int x_left, int y_baseline) const { LLOG("FormulaText::Paint"); LDUMP(text); LDUMP(x_left); LDUMP(y_baseline); draw.DrawText(x_left, y_baseline + deltay, text, font, color); } FormulaRatio::FormulaRatio(RefCon top_, RefCon bottom_, Color color) : top(top_), bottom(bottom_), color(color) { textheight = GetTextHeight0(top, bottom); linewidth = GetLineWidth(textheight); width = max(top->GetWidth(), bottom->GetWidth()); ascent = top->GetHeight() + (linewidth >> 1); descent = bottom->GetHeight() + ((linewidth + 1) >> 1); } void FormulaRatio::Paint(Draw& draw, int x_left, int y_baseline) const { int half = linewidth >> 1; top->PaintRect(draw, x_left + ((width - top->GetWidth()) >> 1), y_baseline - half - top->GetDescent()); draw.DrawLine(x_left + half, y_baseline, x_left + width - half, y_baseline, linewidth, color); draw.DrawRect(x_left, y_baseline - half, width, linewidth, color); bottom->PaintRect(draw, x_left + ((width - bottom->GetWidth()) >> 1), y_baseline + ((linewidth + 1) >> 1) + bottom->GetAscent()); } FormulaSqrt::FormulaSqrt(RefCon in_, RefCon power_, Color color) : in(in_), power(power_), color(color) { int ht = in->GetHeight(), tht = in->GetTextHeight(); leading = GetLeading(tht); linewidth = GetLineWidth(tht); half = linewidth >> 1; descent = in->GetDescent() + leading.cy; ascent = in->GetAscent() + linewidth + leading.cy; width = in->GetWidth(); leftpos = -half; //(ht * 6) >> 4; roofpos = -ascent + leading.cy; slashwd = bslashwd = ht / 10; leftwidth = ht / 10; if(!!power) { ascent = max(ascent, -leftpos + power->GetHeight()); leftwidth = max(leftwidth >> 1, power->GetWidth()) - half; } width += slashwd + bslashwd + leftwidth + half + 2 * leading.cx; textheight = in->GetTextHeight(); } void FormulaSqrt::Paint(Draw& draw, int x_left, int y_baseline) const { int ly = y_baseline + leftpos; int by = y_baseline + descent - leading.cy - half; int ty = y_baseline + roofpos; if(!!power) power->PaintRect(draw, x_left + leading.cx, ly - power->GetDescent()); x_left += leading.cx; in->PaintRect(draw, x_left + leftwidth + slashwd + bslashwd + half, y_baseline); draw.DrawRect(x_left, ly, leftwidth, linewidth, color); x_left += leftwidth; draw.DrawLine(x_left, ly + half, x_left + bslashwd, by, linewidth, color); x_left += bslashwd; draw.DrawLine(x_left, by, x_left + slashwd, ty + half, linewidth, color); x_left += slashwd; draw.DrawRect(x_left, ty, in->GetWidth() + half, linewidth, color); } FormulaIndex::FormulaIndex(RefCon in_, RefCon topright_, RefCon bottomright_, RefCon top_, RefCon bottom_, RefCon topleft_, RefCon bottomleft_) : in(in_) , topright(topright_), bottomright(bottomright_) , top(top_), bottom(bottom_) , topleft(topleft_), bottomleft(bottomleft_) { int trw = 0, tra = 0, trd = 0, brw = 0, bra = 0, brd = 0; int tw = 0, ta = 0, td = 0, bw = 0, ba = 0, bd = 0; int tlw = 0, tla = 0, tld = 0, blw = 0, bla = 0, bld = 0; tlead = blead = 0; if(!!topright) { trw = topright ->GetWidth(); tra = topright ->GetAscent(); trd = topright ->GetDescent(); } if(!!bottomright) { brw = bottomright->GetWidth(); bra = bottomright->GetAscent(); brd = bottomright->GetDescent(); } if(!!top) { tw = top ->GetWidth(); ta = top ->GetAscent(); td = top ->GetDescent(); tlead = GetVertLeading(in->GetTextHeight()); } if(!!bottom) { bw = bottom ->GetWidth(); ba = bottom ->GetAscent(); bd = bottom ->GetDescent(); blead = GetVertLeading(in->GetTextHeight()); } if(!!topleft) { tlw = topleft ->GetWidth(); tla = topleft ->GetAscent(); tld = topleft ->GetDescent(); } if(!!bottomleft) { blw = bottomleft ->GetWidth(); bla = bottomleft ->GetAscent(); bld = bottomleft ->GetDescent(); } rwidth = max(trw, brw); lwidth = max(tlw, blw); bwidth = max(in->GetWidth(), max(tw, bw)); ltdy = rtdy = -max(in->GetAscent() - max(tla, tra), max(tld, trd)); lbdy = rbdy = +max(in->GetDescent() - max(bld, brd), max(bla, bra)); int over = trd + brd - (rbdy - rtdy); if(over > 0) { rtdy -= over >> 1; rbdy += over >> 1; } over = tld + bld - (lbdy - ltdy); if(over > 0) { ltdy -= over >> 1; lbdy += over >> 1; } width = lwidth + bwidth + rwidth; ascent = max(in->GetAscent() + ta + td - tlead, max(!!topleft ? -ltdy + tla : 0, !!topright ? -rtdy + tra : 0)); descent = max(in->GetDescent() + ba + bd - blead, max(!!bottomleft ? lbdy + bld : 0, !!bottomright ? rbdy + brd : 0)); textheight = in->GetTextHeight(); } void FormulaIndex::Paint(Draw& draw, int x_left, int y_baseline) const { in->PaintRect(draw, x_left + lwidth + ((bwidth - in->GetWidth()) >> 1), y_baseline); if(!!topleft) topleft ->PaintRect(draw, x_left + lwidth - topleft ->GetWidth(), y_baseline + ltdy); if(!!bottomleft) bottomleft ->PaintRect(draw, x_left + lwidth - bottomleft ->GetWidth(), y_baseline + lbdy); x_left += lwidth; if(!!top) top ->PaintRect(draw, x_left + ((bwidth - top ->GetWidth()) >> 1), y_baseline - in->GetAscent() - top->GetDescent() + tlead); if(!!bottom) bottom ->PaintRect(draw, x_left + ((bwidth - bottom->GetWidth()) >> 1), y_baseline + in->GetDescent() + bottom->GetAscent() - blead); x_left += bwidth; if(!!topright) topright ->PaintRect(draw, x_left, y_baseline + rtdy); if(!!bottomright) bottomright->PaintRect(draw, x_left, y_baseline + rbdy); } FormulaParen::FormulaParen(RefCon in_, FORMSYMBOL lsym, FORMSYMBOL rsym, Color color) : in(in_), lsymbol(lsym), rsymbol(rsym), color(color) { textheight = in->GetTextHeight(); int rad = max(in->GetAscent(), in->GetDescent()); leading = GetLeading(textheight); Size sz = in->GetSize(); sz.cy -= 2 * leading.cy; if(rad <= textheight) height = textheight + 2 * leading.cy; else height = sz.cy; ascent = max(height >> 1, in->GetAscent()); descent = max(height >> 1, in->GetDescent()); lwidth = SymbolWidth(lsymbol, sz.cy, textheight) + 2 * leading.cx; rwidth = SymbolWidth(rsymbol, sz.cy, textheight) + 2 * leading.cx; width = lwidth + rwidth + sz.cx; } void FormulaParen::Paint(Draw& draw, int x_left, int y_baseline) const { in->PaintRect(draw, x_left + lwidth, y_baseline); int x_right = x_left + lwidth + in->GetWidth(); int hh = height >> 1; PaintSymbol(draw, lsymbol, RectC(x_left, y_baseline - hh, lwidth, height).Deflated(leading), textheight, color); PaintSymbol(draw, rsymbol, RectC(x_right, y_baseline - hh, rwidth, height).Deflated(leading), textheight, color); } FormulaMatrix::FormulaMatrix(pick_ Vector< Vector< RefCon > >& array, pick_ RefCon sep) { Vector points; Vector< RefCon > formulas; for(int i = 0; i < array.GetCount(); i++) for(int j = 0; j < array[i].GetCount(); j++) { points.Add(Point(j, i)); formulas.Add() = array[i][j]; } Init(points, formulas, sep); } FormulaMatrix::FormulaMatrix(pick_ Vector< RefCon >& formulas, RefCon sep) { Vector points; for(int i = 0; i < formulas.GetCount(); i++) points.Add(Point(i, 0)); Init(points, formulas, sep); } void FormulaMatrix::Init(pick_ Vector& pos_, pick_ Vector< RefCon >& formulas_, RefCon sep_) { pos = pos_; formulas = formulas_; sep = sep_; int sw = 0, sa = 0, sd = 0; if(!!sep) { sw = sep->GetWidth(); sa = sep->GetAscent(); sd = sep->GetDescent(); } int i; for(i = 0; i < pos.GetCount(); i++) { const Formula *f = ~formulas[i]; int& wd = widths.DoIndex(pos[i].x, 0); wd = max(wd, f->GetWidth()); int& as = ascents.DoIndex(pos[i].y, sa); as = max(as, f->GetAscent()); int& ds = descents.DoIndex(pos[i].y, sd); ds = max(ds, f->GetDescent()); textheight = max(textheight, f->GetTextHeight()); } Size size(0, 0); xpos.SetCount(widths.GetCount()); for(i = 0; i < widths.GetCount(); i++) { xpos[i] = size.cx; size.cx += widths[i] + sw; } size.cx -= sw; ypos.SetCount(ascents.GetCount()); for(i = 0; i < ascents.GetCount(); i++) { ypos[i] = size.cy; size.cy += ascents[i] + descents[i]; } width = size.cx; if(ascents.GetCount() == 1) { ascent = ascents[0]; descent = descents[0]; } else { ascent = size.cy >> 1; descent = size.cy - ascent; } } void FormulaMatrix::Paint(Draw& draw, int x_left, int y_baseline) const { y_baseline -= ascent; // int max_x = widths.GetCount() - 1; for(int i = 0; i < pos.GetCount(); i++) { const Formula *f = ~formulas[i]; Point xy = pos[i]; int y = y_baseline + ypos[xy.y] + ascents[xy.y]; f->PaintRect(draw, x_left + xpos[xy.x] + ((widths[xy.x] - f->GetWidth()) >> 1), y); int iitem = xy.y * widths.GetCount() + xy.x; if(!!sep && pos.Find(xy + Size(1, 0)) >= 0) sep->PaintRect(draw, x_left + xpos[xy.x] + widths[xy.x], y); } } FormulaIntegral::FormulaIntegral(RefCon integrand, RefCon bottom, RefCon top, bool right_limits) { } void FormulaIntegral::Paint(Draw& draw, int x_left, int y_baseline) const { } inline int GetOperSpace(RefCon a, RefCon b) { return Formula::GetOperSpace(max(a->GetHeight(), b->GetHeight())); } class FormulaParser { public: FormulaParser(const char *ptr, Font font = Arial(100), Color color = Color()); RefCon Run(); private: RefCon RunLow(); RefCon RunSemicolon(); RefCon RunComma(); RefCon RunTextBin(); RefCon RunComp(); RefCon RunMul(); RefCon RunDiv(); RefCon RunPostfix(); RefCon RunTerm(); RefCon RunParen(); bool RunIndex(RefCon& topright, RefCon& bottomright, RefCon& top, RefCon& bottom, RefCon& topleft, RefCon& bottomleft); RefCon GetError(String text, Font font) const; RefCon GetError(String text) const { return GetError(text, state.font); } void AddError(RefCon& item, String text) const; byte Skip(); bool Check(char c); bool Check(char c1, char c2); bool Check(char c1, char c2, char c3); void Force(char c, RefCon& errout); void Force(char c1, char c2, RefCon& errout); String ScanIdent(); Font GetFont(int ht) { return Font(state.font).Height(ht); } void Push(bool makeindex); bool Pop(); RefCon MakeBinary(RefCon left, WString text, RefCon right, int gap = Null, int perc_shift = 0) const; RefCon MakeUnary(String text, RefCon form, int gap = Null, bool symbol = false, int perc_shift = 0) const; private: struct State { Font font; Color color; }; const char *ptr; Array stack; State state; }; RefCon ParseFormula(const char *formula, Font font, Color color) { return FormulaParser(formula, font, color).Run(); } FormulaParser::FormulaParser(const char *ptr, Font font, Color color) : ptr(ptr) { if(font.GetHeight() == 0) font.Height(font.Info().GetHeight()); state.font = font; state.color = color; } RefCon FormulaParser::Run() { if(!Skip()) return NULL; RefCon item = RunLow(); if(Skip()) AddError(item, "ignored: " + StringSample(ptr, 20)); return item; } RefCon FormulaParser::RunLow() { return RunSemicolon(); } RefCon FormulaParser::RunSemicolon() { RefCon item = RunComma(); while(Check(';')) { RefCon right = RunComma(); item = MakeBinary(item, ";", right, Formula::GetTextHeight0(item, right) >> 1); } return item; } RefCon FormulaParser::RunComma() { RefCon item = RunTextBin(); if(Check('&', '&')) { // must run before '&', aargh! RefCon right = RunComma(); int ht = Formula::GetTextHeight0(item, right); return MakeBinary(item, "&", right, Percent(50, ht), ht); } bool row = false; if(Check('&')) row = true; else if(!Check(',')) return item; Vector< Vector< RefCon > > formulas; formulas.Add().Add() = item; int tht = 0; do { tht = max(tht, formulas.Top().Top()->GetTextHeight()); if(row) formulas.Add(); formulas.Top().Add() = RunTextBin(); } while((row = Check('&')) != 0 || Check(',')); return new FormulaMatrix(formulas, new FormulaText(", ", Font(state.font)(tht), state.color)); } RefCon FormulaParser::RunTextBin() { RefCon form = RunComp(); for(String op; !IsNull(op = ScanIdent());) { FORMSYMBOL sym = FS_EMPTY; if (op == "in") sym = (Check('!') ? FS_NOT_MEMBER : FS_MEMBER); else if(op == "subseteq") sym = (Check('!') ? FS_NOT_SUBSET : FS_SUBSET); else if(op == "subset") sym = (Check('!') ? FS_NOT_PROPER_SUBSET : FS_PROPER_SUBSET); else { AddError(form, "??" + op + "??"); continue; } RefCon right = RunComp(); int ht = Percent(150, Formula::GetTextHeight0(form, right)); RefCon body = new FormulaSymbol(sym, ht, ht, state.color); Vector< RefCon > list; list << form << body << right; return new FormulaMatrix(list, new FormulaSpace(Percent(50, ht))); } return form; } RefCon FormulaParser::RunComp() { int tht = state.font.GetHeight(); int gap = 3 * Formula::GetOperSpace(tht); RefCon item = RunMul(); for(;;) if(Check('=', '~')) item = MakeBinary(item, WString(0x2248, 1), RunComp(), gap); else if(Check('=')) item = MakeBinary(item, "=", RunComp(), gap); else if(Check('<', '>')) item = MakeBinary(item, WString(0x2260, 1), RunComp(), gap, -11); else if(Check('<', '=')) item = MakeBinary(item, WString(0x2264, 1), RunComp(), gap, -9); else if(Check('>', '=')) item = MakeBinary(item, WString(0x2265, 1), RunComp(), gap, -9); else if(Check('<', '-', '>')) item = MakeBinary(item, WString(0x2194, 1), RunComp(), Null, -13); else if(Check('<', '-')) item = MakeBinary(item, WString(0x2190, 1), RunComp(), Null, -13); else if(Check('-', '>')) item = MakeBinary(item, WString(0x2192, 1), RunComp(), Null, -13); else if(Check('=', '>')) item = MakeBinary(item, "\xDE", RunComp(), Null, -13); else if(Check('=', '<')) item = MakeBinary(item, "\xDC", RunComp(), Null, -13); else if(Check('<')) item = MakeBinary(item, "<", RunComp(), gap); else if(Check('>')) item = MakeBinary(item, ">", RunComp(), gap); else if(Check('+')) item = MakeBinary(item, "+", RunComp()); else if(Check('-')) item = MakeBinary(item, "-", RunComp(), Null, -11); else if(Check('|', '|')) { RefCon right = RunComp(); Vector< RefCon > list; list << item; int ht = Formula::GetTextHeight0(item, right); list << new FormulaSymbol(FS_LOG_OR, ht, ht, state.color); list << right; return new FormulaMatrix(list, new FormulaSpace(Percent(50, ht))); } else if(Check('|')) item = MakeBinary(item, Null, RunComp(), 0, false); else return item; } RefCon FormulaParser::RunMul() { RefCon item = RunDiv(); for(;;) if(Check('*')) item = MakeBinary(item, "*", RunDiv()); else if(Check('.')) item = MakeBinary(item, ".", RunDiv(), Null, -20); else if(Check('~')) item = MakeBinary(item, Null, RunDiv()); else return item; } RefCon FormulaParser::RunDiv() { RefCon item = RunPostfix(); for(;;) if(Check('/', '_')) item = MakeBinary(item, "/", RunPostfix()); else if(Check('/')) item = new FormulaRatio(item, RunPostfix()); else return item; } RefCon FormulaParser::RunPostfix() { RefCon item = RunTerm(); for(;;) { RefCon topright, bottomright, top, bottom, topleft, bottomleft, in; if(RunIndex(topright, bottomright, top, bottom, topleft, bottomleft)) { if(!!topright || !!bottomright || !!top || !!bottom || !!topleft || !!bottomleft) item = new FormulaIndex(item, topright, bottomright, top, bottom, topleft, bottomleft); } else if(!!(in = RunParen())) { int tht = Formula::GetTextHeight0(item, in); item = MakeBinary(item, Null, in, Formula::GetFuncSpace(tht)); } else if(Check('!')) item = MakeBinary(item, Null, new FormulaText("!", state.font), state.color); else if(Check(':')) item = MakeBinary(item, Null, RunTerm(), 0); else return item; } } bool FormulaParser::RunIndex(RefCon& topright, RefCon& bottomright, RefCon& top, RefCon& bottom, RefCon& topleft, RefCon& bottomleft) { bool done = false; for(;;) { enum KIND { NONE, TR, BR, T, B, TL, BL }; KIND kind = NONE; if(Check('_', '<')) kind = BL; else if(Check('^', '<')) kind = TL; else if(Check('_', '|')) kind = B; else if(Check('^', '|')) kind = T; else if(Check('_')) kind = BR; else if(Check('^')) kind = TR; else if(Check('\'')) { RefPtr form = new FormulaText("\'", Font(state.font).Italic(), state.color, 40); if(!topright) topright = form; else topright = MakeBinary(topright, Null, form, 0); done = true; continue; } if(kind == NONE) return done; Push(true); RefCon ix = RunTerm(); Pop(); switch(kind) { case TR: topright = ix; break; case BR: bottomright = ix; break; case T: top = ix; break; case B: bottom = ix; break; case TL: topleft = ix; break; case BL: bottomleft = ix; break; default: NEVER(); } done = true; } } RefCon FormulaParser::RunParen() { FORMSYMBOL lsym = FS_EMPTY, rsym = FS_EMPTY; char finish = 0; if(Check('(')) { lsym = FS_LPAREN; rsym = FS_RPAREN; finish = ')'; } else if(Check('{', '<')) { lsym = FS_LSHARP; rsym = FS_RSHARP; finish = '}'; } else if(Check('[', '_')) { lsym = FS_LTBRACKET; rsym = FS_RTBRACKET; finish = ']'; } else if(Check('[', '^')) { lsym = FS_LBBRACKET, rsym = FS_RBBRACKET; finish = ']'; } else if(Check('[')) { lsym = FS_LBRACKET; rsym = FS_RBRACKET; finish = ']'; } else if(Check('{', '[', '_')) { lsym = FS_LTBRACKET; rsym = FS_RTBRACKET; finish = '}'; } else if(Check('{', '[', '^')) { lsym = FS_LBBRACKET, rsym = FS_RBBRACKET; finish = '}'; } else if(Check('{', '[')) { lsym = FS_LBRACKET; rsym = FS_RBRACKET; finish = '}'; } else if(Check('{', '|')) { lsym = FS_VBAR; rsym = FS_VBAR; finish = '}'; } else if(Check('{', '(')) { lsym = FS_LPAREN; rsym = FS_RPAREN; finish = '}'; } else if(Check('{', '_')) { lsym = FS_LBRACE; rsym = FS_RBRACE; finish = '}'; } else if(Check('{', '~')) { lsym = FS_EMPTY; rsym = FS_RBRACE; finish = '}'; } else if(Check('{')) { finish = '}'; } else return NULL; RefCon item = RunLow(); if(finish == '}') { if(Check('}', ']', '_')) rsym = FS_RBBRACKET; else if(Check('}', ']', '^')) rsym = FS_RTBRACKET; else if(Check('}', ']')) rsym = FS_RBRACKET; else if(Check('}', ')')) rsym = FS_RPAREN; else if(Check('}', '>')) rsym = FS_RSHARP; else if(Check('}', '|')) rsym = FS_VBAR; else if(Check(finish, '~')) rsym = FS_EMPTY; else Force(finish, item); } else Force(finish, item); if(lsym != FS_EMPTY || rsym != FS_EMPTY) return new FormulaParen(item, lsym, rsym, state.color); return item; } RefCon FormulaParser::RunTerm() { RefCon paren = RunParen(); if(!!paren) return paren; if(Check('@')) return MakeUnary("\xB6", RunTerm(), 0, true); if(Check('-')) return MakeUnary("-", RunTerm(), 0, true); if(Check('+')) return MakeUnary("+", RunTerm(), 0, true); if(Check('.', '.', '.')) return new FormulaText("...", state.font, state.color); if(Check('.', '.')) return new FormulaText("..", state.font, state.color); if(Check('~', '~')) { int blanks = 1; for(; *ptr == '~'; ptr++) blanks++; RefCon item = RunTerm(); return MakeUnary(Null, item, (blanks * Formula::GetTextHeight0(item)) >> 1); } Font font = state.font; bool noitalic = false; for(;;) if(Check('*')) font.Bold(); else if(Check('/')) font.Italic(); else if(Check('\\')) noitalic = true; else break; if(Check(':')) { static const wchar Alpha[26] = { 0x391, // A -> Alpha 0x392, // B -> Beta 0x3A7, // C -> Chi 0x394, // D -> Delta 0x395, // E -> Epsilon 0x3A6, // F -> Phi 0x393, // G -> Gamma 0x397, // H -> Eta 0x399, // I -> Iota 0x300, // J 0x39A, // K -> Kappa 0x39B, // L -> Lambda 0x39C, // M -> Mu 0x39D, // N -> Nu 0x39F, // O -> Omikron 0x3A0, // P -> Pi 0x398, // Q -> Theta 0x3A1, // R -> Rho 0x3A3, // S -> Sigma 0x3A4, // T -> Tau 0x300, // U 0x3A9, // V -> Omega 0x3A8, // W -> Psi 0x39E, // X -> Xi 0x3A5, // Y -> Ypsilon 0x396, // Z -> Zeta }; static const wchar alpha[26] = { 0x3B1, // a -> alpha 0x3B2, // b -> beta 0x3C7, // c -> chi 0x3B4, // d -> delta 0x3B5, // e -> epsilon 0x3C6, // f -> phi 0x3B3, // g -> gamma 0x3B7, // h -> eta 0x3B9, // i -> iota 0x300, // j 0x3BA, // k -> kappa 0x3BB, // l -> lambda 0x3BC, // m -> mu 0x3BD, // n -> nu 0x3BF, // o -> omikron 0x3C0, // p -> pi 0x3B8, // q -> theta 0x3C1, // r -> rho 0x3C3, // s -> sigma 0x3C4, // t -> tau 0x3C2, // u -> final sigma 0x3C9, // v -> omega 0x3C8, // w -> psi 0x3BE, // x -> xi 0x3C5, // y -> ypsilon 0x3B6, // z -> zeta }; wchar wc = 0; if(IsUpper(*ptr)) wc = Alpha[*ptr++ - 'A']; else if(IsLower(*ptr)) wc = alpha[*ptr++ - 'a']; else return GetError("invalid greek character"); return new FormulaText(WString(wc, 1), font, state.color); } char c = Skip(); if(IsAlpha(c)) { const char *b = ptr; while(IsAlNum(*++ptr)) ; String ident(b, ptr); FORMSYMBOL group_sym = FS_EMPTY; if(ident == "sqrt") { RefCon exp; if(Check('_')) { Push(true); exp = RunTerm(); Pop(); } RefCon arg = RunTerm(); return new FormulaSqrt(arg, exp); } else if(ident == "not") group_sym = FS_LOG_NOT; else if(ident == "sum") group_sym = FS_SUM; else if(ident == "product") group_sym = FS_PRODUCT; else if(ident == "union") group_sym = FS_UNION; else if(ident == "intersection") group_sym = FS_INTERSECTION; else if(ident == "int") group_sym = FS_INT; else if(ident == "iint") group_sym = FS_IINT; else if(ident == "iiint") group_sym = FS_IIINT; else if(ident == "oint") group_sym = FS_OINT; else if(ident == "oiint") group_sym = FS_OIINT; if(group_sym != FS_EMPTY) { RefCon topright, bottomright, top, bottom, topleft, bottomleft; RunIndex(topright, bottomright, top, bottom, topleft, bottomleft); RefCon arg = RunTerm(); int ht = arg->GetTextHeight(); if(group_sym != FS_LOG_NOT) ht = max(ht, Percent(150, max(arg->GetAscent(), arg->GetDescent()))); RefCon body = new FormulaSymbol(group_sym, ht, arg->GetTextHeight(), state.color); if(!!topright || !!bottomright || !!top || !!bottom || !!topleft || !!bottomleft) body = new FormulaIndex(body, topright, bottomright, top, bottom, topleft, bottomleft); return MakeBinary(body, Null, arg); } else if(ident == "oo") return new FormulaText(WString(0x221E, 1), font, state.color, -11); if(!font.IsItalic() && !noitalic && ptr - b == 2 && *b == 'd' && (b[1] < 'd' || b[1] > 'h')) return MakeBinary( new FormulaText("d", font, state.color), Null, new FormulaText(String(b + 1, ptr).ToWString(), Font(font).Italic(), state.color), 0); if(ptr - b == 1 && (*b < 'd' || *b > 'h')) font.Italic(); if(noitalic) font.NoItalic(); return new FormulaText(String(b, ptr).ToWString(), font, state.color); } if(IsDigit(c)) { const char *b = ptr; while(IsDigit(*++ptr)) ; if(*ptr == '.' && IsDigit(ptr[1])) { while(IsDigit(*++ptr)) ; } RefCon number = new FormulaText(String(b, ptr).ToWString(), state.font, state.color); if(*ptr == 'e' || *ptr == 'E') { b = ++ptr; if(*ptr == '+' || *ptr == '-') ptr++; RefCon exp; Font fx = Font(font).Height(Formula::GetIndexSize(font.GetHeight())); if(IsDigit(*ptr)) { while(IsDigit(*++ptr)) ; exp = new FormulaText(String(b, ptr).ToWString(), fx, state.color); } else exp = GetError("missing exponent", fx); number = MakeBinary(number, ".", new FormulaIndex(new FormulaText("10", font, state.color), exp), Null, -20); } return number; } if(c == '\"') { String out; while(*++ptr && *ptr != '\"') if(*ptr == '\\' && ptr[1]) out.Cat(*++ptr); else out.Cat(*ptr); RefPtr item = new FormulaText(out.ToWString(), font, state.color); Force('\"', item); return item; } if(c == '~') { ptr++; return new FormulaSpace(0, 0, 0, state.font.GetHeight()); } String err; if(*ptr) err << (char)c << " (0x" << Format("%02x", c) << ")?"; else err = "EOF"; return GetError(err, state.font); } void FormulaParser::Push(bool makeindex) { stack.Add(state); if(makeindex) state.font.Height(Formula::GetIndexSize(state.font.GetHeight())); } bool FormulaParser::Pop() { if(stack.IsEmpty()) return false; state = stack.Top(); stack.Drop(); return true; } byte FormulaParser::Skip() { for(;;) if(*ptr && (byte)*ptr <= ' ') ptr++; else if(*ptr == '/' && ptr[1] == '*') { ptr += 2; while(*ptr && !(ptr[0] == '*' && ptr[1] == '/')) ptr++; if(*ptr) ptr += 2; } else if(ptr[0] == '/' && ptr[1] == '/') { ptr += 2; while(*ptr && *ptr != '\n') ptr++; } else return *ptr; } String FormulaParser::ScanIdent() { byte c = Skip(); if(!IsAlpha(c)) return Null; const char *b = ptr; while(IsAlNum(*ptr)) ptr++; return String(b, ptr); } bool FormulaParser::Check(char c) { if(Skip() != c) return false; ptr++; return true; } bool FormulaParser::Check(char c1, char c2) { if(Skip() != c1 || ptr[1] != c2) return false; ptr += 2; return true; } bool FormulaParser::Check(char c1, char c2, char c3) { if(Skip() != c1 || ptr[1] != c2 || ptr[2] != c3) return false; ptr += 3; return true; } RefCon FormulaParser::MakeBinary(RefCon left, WString text, RefCon right, int gap, int perc_shift) const { int ht = Formula::GetTextHeight0(left, right); if(IsNull(gap)) gap = Formula::GetOperSpace(ht); Vector< RefCon > formula; formula.Add() = left; if(!IsNull(text)) { Font f = state.font; f.Height(ht); formula.Add() = new FormulaText(text, f, state.color, perc_shift); } formula.Add() = right; RefPtr form = new FormulaMatrix(formula, new FormulaSpace(gap)); //form = new FormulaBox(form, LtRed); return form; } RefCon FormulaParser::MakeUnary(String text, RefCon form, int gap, bool symbol, int perc_shift) const { int ht = form->GetTextHeight(); Vector< RefCon > formula; if(!IsNull(text)) { Font f = state.font; if(symbol) f.Face(Font::SYMBOL); f.Height(ht); formula.Add() = new FormulaText(text.ToWString(), f, state.color, perc_shift); } formula.Add() = form; return new FormulaMatrix(formula, new FormulaSpace(gap)); } RefCon FormulaParser::GetError(String text, Font font) const { return new FormulaText(text.ToWString(), font.Bold(), LtRed); } void FormulaParser::AddError(RefCon& item, String text) const { int ht = item->GetTextHeight(); item = MakeBinary(item, Null, GetError(text, Font(state.font).Height(ht))); } void FormulaParser::Force(char c, RefCon& errout) { if(!Check(c)) { char temp[] = { c, 0 }; Vector< RefCon > list; list.Add() = errout; Font fb = Font(state.font).Bold(); list.Add() = new FormulaText(temp, fb, LtBlue); list.Add() = new FormulaText(*ptr ? ~StringSample(ptr, 1) : "EOF", fb, LtRed); errout = new FormulaMatrix(list); } } void FormulaParser::Force(char c1, char c2, RefCon& errout) { if(!Check(c1, c2)) { char temp[] = { c1, c2, 0 }; Vector< RefCon > list; list.Add() = errout; Font fb = Font(state.font).Bold(); list.Add() = new FormulaText(temp, fb, LtBlue); list.Add() = new FormulaText(*ptr ? ~StringSample(ptr, 2) : "EOF", fb, LtRed); errout = new FormulaMatrix(list); } } FormulaDisplay::FormulaDisplay(int sh) : std_height(sh) { } void FormulaDisplay::Paint(Draw& draw, const Rect& rc, const Value& value, Color ink, Color paper, dword style) const { draw.DrawRect(rc, paper); RefCon form = Get(value, std_height ? std_height : 300, ink); if(!!form) { if(std_height) form->PaintRect(draw, (rc.left + rc.right - form->GetWidth()) >> 1, (rc.top + rc.bottom - form->GetHeight() + 2 * form->GetAscent()) >> 1); else { DrawingDraw ddraw(form->GetSize()); form->PaintRect(ddraw, 0, form->GetAscent()); Drawing dwg = ddraw; Size outsize = GetScaledSize(dwg.GetSize(), rc.Size()); draw.DrawDrawing(rc.CenterRect(outsize), dwg); } } } Size FormulaDisplay::GetStdSize(const Value& value) const { int ht = std_height; if(!ht) { FontInfo fi = StdFont().Info(); ht = fi.GetHeight() - fi.GetExternal(); } RefCon form = Get(value, ht, SBlack); if(!!form) return form->GetSize(); return Size(0, 0); } RefCon FormulaDisplay::Get(const Value& value, int ht, Color ink) const { if(IsNull(value)) return NULL; return ParseFormula(StdFormat(value), Roman(ht), ink); // return new FormulaBox(ParseFormula(StdFormat(value), Roman(ht)), LtRed, FormulaBox::INNER); } const Display& GLOBAL_V(FormulaDisplay, StdFormulaDisplay); #ifdef flagMAIN #include void AppMain() { FormulaDisplay fd(0); TopWindow tw; tw.Sizeable().Zoomable(); const char *t; // t = "[_9 & 8 & 7]+[^9 & 8 & 7]+{_6 & 7 & 8}"; // t = "(1,2 & 3 + 2 ,4 & 3,5 & 4) + ( x & y & z & w)=(0 & 0 & 0 & 0)"; // t = "t^3=sqrt(3^2+2^2)"; // t = "t^3=1/sqrt{{3^2+2^2}/a_A^sqrt_5(t^3+1)}"; // t = "t^3=1/sqrt_{4 & 5 & 6 & 7 & 8 & 9}{{3^2+2^2}/a_A^sqrt_5(t^3+1)}"; // t = "B(:a, :b, :c, t) = :a*(1-t)^2+:b*2*t*(1-t)+:c*t^2"; // t = "sin{:a+:b}=sin{:a}*cos{:b}+sin{:a}*cos{:b}"; // t = "a^n+b^n=c^n"; // t = "a^2+b^2 <= 2*a*b"; // t = "/cosh{x}=sqrt{1+sinh_|2^|2^2{x}}"; // t = "sum_|{i=0}^|10{i^2}"; // t = "iint_|{x=0}^|10{i~x^2~dx} & int{x^2~dx} & iiint{x^2~d/x}"; // t = "n!=int_|{x=0}^|oo{x^{n-1}~e^x~dx}"; // t = "n!<>0=iint_|{x,y=0}^|oo{x^{n-1}~e^x~@x~@y}"; // t = "0=0+0-0<>0<-0 & x->oo<->0 & 0<=0>=0<0>0"; // t = "e=lim_|{x->oo}(1^x+1/x)^x"; // t = ":x(x)=sum_|{k=0}^|oo{{(x-a)^k~:x^(k)(a)}/k!} & \"\"=:x(a)+(x-a)~d::x(a)/dx" // "+(x-a)^2/2!~d^2::x(a)/dx^2+(x-a)^3/3!~d^3::x(a)/dx^3+..."; t = "oiint_|{:j}{r~dA}={:p~r^2}/4"; tw.Background(PaintRect(fd, t)); tw.Run(); } #endif END_UPP_NAMESPACE