#include "RichText.h" namespace Upp { #define IMAGECLASS RichTextImg #define IMAGEFILE #include Color PaintInfo::ResolveInk(Color ink) const { return Nvl(textcolor, darktheme ? Nvl(DarkThemeCached(ink), SColorText()) : Nvl(ink, SColorText())); } Color PaintInfo::ResolvePaper(Color paper) const { return IsNull(paper) ? paper : darktheme ? DarkThemeCached(paper) : paper; } RichPara::Lines RichPara::Begin(RichContext& rc) const { Lines pl = FormatLines(rc.page.Width(), rc.number); rc.py.y += format.before + format.ruler; pl.Justify(format); return pl; } void RichPara::Flush(Draw& draw, const PaintInfo& pi, wchar *text, const CharFormat **i0, int *wd, int pos, int len, int x0, int x, int y0, int y, int linecy, int lineascent, Zoom z, bool highlight) { if(*i0) { const CharFormat& f = **i0; int width = z * x - z * x0; int zy0 = z * y0; int zx0 = z * x0; if(!IsNull(f.indexentry) && !IsNull(pi.indexentry)) { if(pi.indexentrybg) draw.DrawRect(zx0, z * y, width, z * (y + linecy) - z * y, pi.ResolvePaper(pi.indexentry)); else draw.DrawRect(zx0, zy0, width, 2, pi.indexentry); } if(!IsNull(f.paper) && !highlight && IsNull(pi.textcolor)) draw.DrawRect(zx0, z * y, width, z * (y + linecy) - z * y, pi.ResolvePaper(f.paper)); Font fnt = f; int zht = z * tabs(f.GetHeight()); int ssa = 0; int ssd = 0; if(f.sscript) { FontInfo fi = fnt(zht).Info(); ssa = fi.GetAscent(); ssd = fi.GetDescent(); zht = 3 * zht / 5; } fnt.Height(zht ? zht : 1); FontInfo fi = fnt.Info(); Color ink = pi.ResolveInk(f.ink); if(f.dashed) { int dx = max(fi.GetAscent() / 5, 2); for(int i = 0; dx * i < width; i++) draw.DrawRect(zx0 + i * dx, zy0 + fi.GetDescent() / 2, dx / 2, max(fi.GetDescent() / 3, 1), ink); } if(!IsNull(f.link) && !IsNull(pi.hyperlink) && !(fnt.IsUnderline() || f.dashed)) { fnt.Underline(); if(!pi.darktheme && IsNull(pi.textcolor)) ink = pi.hyperlink; } x = zx0; Vector< Tuple2 > bak; if(f.link.GetCount()) draw.Escape("url:" + f.link); for(int i = 0; i < len; i++) { int w = wd[pos + i]; Image img; wchar& c = text[pos + i]; if(c == ' ') img = RichTextImg::SpaceChar(); if(c == 160) img = RichTextImg::HardSpaceChar(); if(c >= 9 && c < 9 + 4) { if(c > 9) { static char fct[] = { ' ', '.', '-', '_' }; int fc = fct[c - 9]; int pw = fi[fc]; int px = (x + pw - 1) / pw * pw; while(px + pw < x + w) { draw.DrawText(px, zy0 - fi.GetAscent(), String(fc, 1), fnt, Black); px += pw; } } img = RichTextImg::TabChar(); bak.Add(MakeTuple(pos + i, c)); c = ' '; } if(img && !IsNull(pi.showcodes)) { Size sz = img.GetSize(); if(sz.cy < z * lineascent) draw.DrawImage(x + (w - sz.cx) / 2, zy0 - sz.cy, img, pi.showcodes); } x += w; } draw.DrawText(zx0, f.sscript == 1 ? zy0 - ssa : f.sscript == 2 ? zy0 + ssd - fi.GetHeight() : zy0 - fi.GetAscent(), text + pos, fnt, ink, len, wd + pos); for(int i = 0; i < bak.GetCount(); i++) text[bak[i].a] = bak[i].b; if(f.link.GetCount()) draw.Escape("url:"); } } bool RichPara::BreaksPage(const RichContext& rc, const Lines& pl, int i) const { int linecy = pl[i].Sum(); if(linecy >= rc.page.Height()) return false; if(linecy + rc.py.y > rc.page.bottom) return true; if(format.orphan || pl.GetCount() < 2) return false; if((i == 0 || i == pl.GetCount() - 2) && rc.py.y + linecy + pl[i + 1].Sum() > rc.page.bottom) return true; return false; } struct RichObjectImageMaker : ImageMaker { RichObject object; Size sz; void *context; virtual String Key() const; virtual Image Make() const; }; String RichObjectImageMaker::Key() const { StringBuffer b; RawCat(b, object.GetSerialId()); RawCat(b, sz); RawCat(b, context); return String(b); } Image RichObjectImageMaker::Make() const { return object.ToImage(sz, context); } void RichPara::DrawRuler(Draw& w, int x, int y, int cx, int cy, Color ink, int style) { int segment = cy; int r = x + cx; switch(style) { case RULER_DASH: segment = 2 * cy; case RULER_DOT: while(x < r) { w.DrawRect(x, y, min(r - x, segment), cy, ink); x += 2 * segment; } break; default: w.DrawRect(x, y, cx, cy, ink); break; } } void RichPara::Paint(PageDraw& pw, RichContext rc, const PaintInfo& pi, const Number& n, const Bits& spellerror, bool baselevel) const { Zoom z = pi.zoom; PageY opy = rc.py; Lines pl = Begin(rc); if(pw.tracer) { PageY h = rc.py; h.y -= format.before + format.ruler; pw.tracer->Paragraph(rc.page, h, *this); } bool highlight = pi.highlightpara >= 0 && pi.highlightpara < pl.len || pi.WhenHighlight(format.label); int hy = rc.py.y - format.before - format.ruler; int phy = rc.py.page; if(format.ruler && hy >= 0 && hy + format.ruler < rc.page.bottom) DrawRuler(pw.Page(phy), z * rc.page.left + z * format.lm, z * hy, z * rc.page.right - z * rc.page.left - z * format.rm - z * format.lm, max(1, z * format.ruler), pi.ResolveInk(format.rulerink), format.rulerstyle); if(pi.sell < 0 && pi.selh > 0) for(int p = opy.page; p <= rc.py.page; p++) { int top = z * (p == opy.page ? opy.y : rc.page.top); int bottom = z * (p == rc.py.page ? rc.py.y : rc.page.bottom); pi.DrawSelection(pw.Page(p), z * rc.page.left, top, z * rc.page.right - z * rc.page.left, bottom - top); } opy = rc.py; int x = 0; int y0 = 0; int lineascent = 0; PageY mpy; int mla = Null; for(int lni = 0; lni < pl.GetCount(); lni++) { const Line& li = pl[lni]; int linecy = li.Sum(); lineascent = li.ascent; if(BreaksPage(rc, pl, lni)) { if(li.ppos > pi.sell && li.ppos < pi.selh) { int y = z * rc.py.y; pi.DrawSelection(pw.Page(rc.py.page), z * rc.page.left, y, z * rc.page.right - z * rc.page.left, z * rc.page.bottom - y); } rc.Page(); } if(rc.py > pi.bottom) break; if(lni == 0) { mpy = rc.py; mla = lineascent; } const CharFormat **cf = pl.format + li.pos; const CharFormat **i = cf; const CharFormat **ilim = i + li.len; const HeightInfo *hg = pl.height + li.pos; if(rc.py + linecy >= pi.top) { Draw& draw = pw.Page(rc.py.page); #ifdef _DEBUG int cloff = draw.GetCloffLevel(); #endif Buffer wd(li.len); int x0 = li.xpos + rc.page.left; x = x0; int *w = pl.width + li.pos; int *wl = w + li.len; int *t = wd; while(w < wl) { int x1 = x + *w++; *t++ = z * x1 - z * x; x = x1; } if(highlight) draw.DrawRect(z * rc.page.left, z * rc.py.y, z * rc.page.Width(), z * (rc.py.y + linecy) - z * rc.py.y, pi.highlight); const CharFormat **i0 = i; wchar *text = pl.text + li.pos; x = x0; w = pl.width + li.pos; y0 = rc.py.y + li.ascent; int pp = li.pos; int l = rc.page.right; int h = -1; while(i < ilim) { if(pl.pos[pp] == pi.sell && x < l) l = x; if(pl.pos[pp] == pi.selh && h < 0) h = x; pp++; if(*i0 != *i || hg->object) { Flush(draw, pi, text, i0, wd, (int)(i0 - cf), (int)(i - i0), x0, x, y0, rc.py.y, linecy, lineascent, z, highlight); i0 = i; x0 = x; } if(hg->object) { const RichObject& o = *hg->object; if(o) { Size sz = z * o.GetSize(); int ix = z * x; if(pi.shrink_oversized_objects && sz.cx + ix > rc.page.right) sz.cx = rc.page.right - ix; draw.DrawRect(ix, z * rc.py.y, sz.cx, z * linecy, (*i)->paper); draw.Clipoff(ix, z * (y0 - hg->ascent), sz.cx, sz.cy); if(pi.sizetracking) draw.DrawRect(sz, SColorFace); else if(pi.usecache) { RichObjectImageMaker im; im.object = o; im.sz = sz; im.context = pi.context; draw.DrawImage(0, 0, MakeImagePaintOnly(im)); } else o.Paint(draw, sz, pi.context); draw.End(); } i++; hg++; x += *w++; i0 = i; x0 = x; } else { i++; hg++; x += *w++; } } if(i > i0) Flush(draw, pi, text, i0, wd, (int)(i0 - cf), (int)(i - i0), x0, x, y0, rc.py.y, linecy, lineascent, z, highlight); if(lni == 0) { Rect r; r.left = rc.page.left + format.lm; int q = li.ascent / 2; r.top = rc.py.y + 4 * (li.ascent - q) / 5; r.right = r.left + q; r.bottom = r.top + q; q = z * (r.Width() / 4); r.left = z * r.left; r.top = z * r.top; r.right = z * r.right; r.bottom = z * r.bottom; Rect r1 = r; r1.Deflate(max(1, q)); Color bullet_ink = format.ink; if(format.bullet) { if(part.GetCount()) bullet_ink = part[0].format.ink; } switch(format.bullet) { case BULLET_BOX: draw.DrawRect(r, pi.ResolveInk(bullet_ink)); break; case BULLET_BOXWHITE: draw.DrawRect(r, pi.ResolveInk(bullet_ink)); draw.DrawRect(r1, pi.ResolvePaper(White())); break; case BULLET_ROUNDWHITE: draw.DrawEllipse(r, pi.ResolveInk(bullet_ink)); draw.DrawEllipse(r1, pi.ResolvePaper(White())); break; case BULLET_ROUND: draw.DrawEllipse(r, pi.ResolveInk(bullet_ink)); break; } } int zlcy = z * linecy; if(pi.spellingchecker && zlcy > 3) { int x = z * (li.xpos + rc.page.left); w = wd; wl = w + li.len; int i = li.pos; int q = z * (rc.py.y + linecy); while(w < wl) { if(spellerror[pl.pos[i]] && (lni || (i >= pl.number_chars))) { if(zlcy > 16) draw.DrawRect(x, q - 3, *w, 1, Red); draw.DrawRect(x, q - 2, *w, 1, LtRed); } x += *w++; i++; } } if(pi.selh == li.ppos + li.plen) h = x; if(pi.sell == li.ppos + li.plen) l = x; if(pi.sell < li.ppos) l = rc.page.left; if(pi.selh > li.ppos + li.plen) h = rc.page.right; if(pi.sell < pi.selh && pi.selh > li.ppos) pi.DrawSelection(draw, z * l, z * rc.py.y, z * h - z * l, z * (rc.py.y + linecy) - z * rc.py.y); ASSERT(draw.GetCloffLevel() == cloff); } else while(i < ilim) { i++; hg++; } rc.py.y += linecy; } Size sz = RichTextImg::EndParaChar().GetSize(); if(sz.cy < z * lineascent && !IsNull(pi.showcodes)) pw.Page(rc.py.page).DrawImage(z * x, z * y0 - sz.cy, RichTextImg::EndParaChar(), format.indexentry.GetCount() ? pi.indexentry : pi.showcodes); if(format.newpage && !IsNull(pi.showcodes)) { Draw& w = pw.Page(opy.page); int wd = z * rc.page.right - z * rc.page.left; int step = w.Pixels() ? 8 : 50; int y = z * opy.y; for(int x = 0; x < wd; x += step) w.DrawRect(z * rc.page.left + x, y, step >> 1, step >> 3, pi.showcodes); } if(pl.len >= pi.sell && pl.len < pi.selh && rc.py < pi.bottom) { int top = z * rc.py.y; pi.DrawSelection(pw.Page(rc.py.page), z * rc.page.left, top, z * rc.page.right - z * rc.page.left, z * min(rc.py.y + format.after, rc.page.bottom) - top); } if(!IsNull(mla) && !IsNull(pi.showcodes) && pi.showlabels) { bool b = format.label.GetCount(); Image img = RichTextImg::Label(); for(int pass = 0;;pass++) { Size isz = img.GetSize(); if(b) pw.Page(mpy.page).DrawImage(-7 - isz.cx, z * mpy.y + (z * mla - isz.cy) / 2, img, pi.showcodes); if(pass) break; b = format.newhdrftr && baselevel; img = RichTextImg::HdrFtr(); } } if(pw.tracer) pw.tracer->EndParagraph(rc.py); } void RichPara::GetRichPos(RichPos& rp, int pos) const { rp.format = format; int i = FindPart(pos); if(i < GetCount()) { const Part& p = part[i]; (CharFormat&)rp.format = p.format; if(p.object) { rp.chr = -1; rp.object = p.object; } else if(p.IsText()) rp.chr = p.text[pos]; else { rp.chr = -2; rp.field = p.field; rp.fieldparam = p.fieldparam; if(p.fieldpart.GetCount() > 0) rp.fieldformat = p.fieldpart[0].format; } } else rp.chr = '\n'; } RichCaret RichPara::GetCaret(int pos, RichContext rc) const { Lines pl = Begin(rc); RichCaret pr; FontInfo fi = format.Info(); pr.caretascent = fi.GetAscent(); pr.caretdescent = fi.GetDescent(); for(int lni = 0; lni < pl.GetCount(); lni++) { Line& li = pl[lni]; int linecy = li.Sum(); if(BreaksPage(rc, pl, lni)) rc.Page(); pr.page = rc.py.page; pr.top = rc.py.y; pr.bottom = rc.py.y + linecy; pr.lineascent = li.ascent; pr.line = lni; if(pos < li.ppos + li.plen) { int npos = li.pos; int x = li.xpos + rc.page.left; if(lni == 0 && pl.number_chars) { // skip the number npos += pl.number_chars; for(int i = 0; i < pl.number_chars; i++) x += pl.width[li.pos + i]; } int *w = pl.width + npos; int *p = pl.pos + npos; const CharFormat **i = pl.format + npos; const HeightInfo *h = pl.height + npos; if(li.len && *i) { pr.caretascent = h->ascent; pr.caretdescent = h->descent; } while(pos > *p) { // second condition to skip the number x += *w++; if(*i) { pr.caretascent = h->ascent; pr.caretdescent = h->descent; } h++; i++; p++; } pr.objectcy = h->ascent + h->ydelta; pr.objectyd = h->ydelta; pr.left = x; pr.right = x + *w; pr.caretdescent = min(pr.caretdescent, pr.Height() - pr.lineascent); return pr; } rc.py.y += linecy; } const Line& li = pl.line.Top(); pr.left = li.cx + li.xpos + rc.page.left; pr.right = rc.page.right; pr.caretdescent = min(pr.caretdescent, pr.Height() - pr.lineascent); return pr; } int RichPara::PosInLine(int x, const Rect& page, const Lines& pl, int lni) const { const Line& li = pl[lni]; int xp = li.xpos + page.left; int npos = li.pos; if(lni == 0 && pl.number_chars) { // skip the number npos += pl.number_chars; for(int i = 0; i < pl.number_chars; i++) xp += pl.width[li.pos + i]; } const int *w = pl.width + npos; const int *wl = w + li.len; if(lni < pl.GetCount() - 1 && li.len > 0 && pl.text[npos + li.len - 1] == ' ') wl--; while(w < wl && xp + *w <= x) xp += *w++; int pos = (int)(w - pl.width); return pos < pl.clen ? pl.pos[pos] : pl.len; } int RichPara::GetPos(int x, PageY y, RichContext rc) const { Lines pl = Begin(rc); if(pl.len) for(int lni = 0; lni < pl.GetCount(); lni++) { const Line& li = pl[lni]; int linecy = li.Sum(); if(BreaksPage(rc, pl, lni)) rc.Page(); rc.py.y += linecy; if(y < rc.py || lni == pl.GetCount() - 1) return PosInLine(x, rc.page, pl, lni); } return pl.len; } int RichPara::GetVertMove(int pos, int gx, const Rect& page, int dir, const RichContext& rc) const { Lines pl = FormatLines(page.Width(), rc.number); int lni; if(pos >= 0) { for(lni = 0; lni < pl.GetCount() - 1; lni++) { const Line& li = pl[lni]; if(pos < li.ppos + li.plen) break; } lni += sgn(dir); if(lni < 0 || lni >= pl.GetCount() || lni == 0 && pl.number_chars == pl[lni].len) return -1; } else lni = dir < 0 ? pl.GetCount() - 1 : 0; return PosInLine(gx, page, pl, lni); } void RichPara::GatherLabels(Vector& info, RichContext rc, int pos) const { Lines pl = Begin(rc); WString ie; if(!pl.GetCount()) return; if(BreaksPage(rc, pl, 0)) rc.Page(); if(format.label.IsEmpty()) return; RichValPos& f = info.Add(); f.py = rc.py; f.pos = pos; f.data = format.label.ToWString(); } void RichPara::GatherIndexes(Vector& info, RichContext rc, int pos) const { Lines pl = Begin(rc); WString ie; for(int lni = 0; lni < pl.GetCount(); lni++) { Line& li = pl[lni]; int linecy = li.Sum(); if(BreaksPage(rc, pl, lni)) rc.Page(); const CharFormat **i0 = pl.format + li.pos; const CharFormat **i = i0; const CharFormat **ilim = i + li.len; while(i < ilim) { if(*i && (*i)->indexentry != ie) { ie = (*i)->indexentry; if(!ie.IsEmpty()) { RichValPos& f = info.Add(); f.py = rc.py; f.pos = (int)(i - i0) + pos; f.data = ie; } } i++; } rc.py.y += linecy; } } void FontHeightRound(Font& fnt, Zoom z) { fnt.Height(max((fnt.GetHeight() * z.m + (z.d >> 1)) / z.d, 1)); // do not allow to fall to zero (that is default font height) } void operator*=(RichPara::Format& format, Zoom z) { FontHeightRound(format, z); format.before *= z; if(format.ruler) { format.ruler *= z; if(format.ruler == 0) format.ruler = 1; } int ll = format.lm + format.indent; format.lm *= z; format.indent = z * ll - format.lm; format.rm *= z; format.after *= z; format.tabsize *= z; for(int i = 0; i < format.tab.GetCount(); i++) { int& pos = format.tab[i].pos; pos = ((pos & ~RichPara::TAB_RIGHTPOS) * z) | (pos & RichPara::TAB_RIGHTPOS); } } void RichPara::ApplyZoom(Zoom z) { format *= z; for(int i = 0; i < part.GetCount(); i++) if(part[i].IsText()) FontHeightRound(part[i].format, z); else if(part[i].object) part[i].object.SetSize(z * part[i].object.GetSize()); } }