#include "RichText.h" namespace Upp { void RichPara::Smh(Lines& lines, HeightInfo *th, int cx) const { Line& l = lines.line.Top(); l.ascent = l.descent = l.external = 0; const HeightInfo *he = th + l.pos + l.len; for(const HeightInfo *h = th + l.pos; h < he; h++) { if(h->ascent > l.ascent) l.ascent = h->ascent; if(h->descent > l.descent) l.descent = h->descent; if(h->external > l.external) l.external = h->external; } if(format.linespacing == LSP115) { l.ascent = 115 * l.ascent / 100; l.descent = 115 * l.descent / 100; } if(format.linespacing == LSP15) { l.ascent = (3 * l.ascent) >> 1; l.descent = (3 * l.descent) >> 1; } if(format.linespacing == LSP20) { l.ascent = 2 * l.ascent; l.descent = 2 * l.descent; } l.xpos = format.lm; cx -= format.lm + format.rm; l.xpos += lines.GetCount() == 1 ? lines.first_indent : lines.next_indent; if(!l.withtabs) switch(format.align) { case ALIGN_RIGHT: l.xpos += cx - l.cx; break; case ALIGN_CENTER: l.xpos += (cx - l.cx) / 2; break; } l.cx -= lines.GetCount() == 1 ? lines.first_indent : lines.next_indent; } RichPara::Tab RichPara::GetNextTab(int pos, int cx) const { int tabi = -1; int dist = INT_MAX; for(int i = 0; i < format.tab.GetCount(); i++) { const Tab& tab = format.tab[i]; int tabpos = tab.pos; if(tabpos & TAB_RIGHTPOS) tabpos = cx - (tabpos & ~TAB_RIGHTPOS); if(tabpos > pos && tabpos - pos < dist) { tabi = i; dist = tabpos - pos; } } if(format.bullet == BULLET_TEXT) { int q = format.indent + format.lm; if(q > pos && q - pos < dist) { Tab tab; tab.align = ALIGN_LEFT; tab.pos = q; return tab; } } if(tabi < 0) { Tab tab; tab.pos = format.tabsize ? (pos + format.tabsize) / format.tabsize * format.tabsize : 0; tab.align = ALIGN_LEFT; return tab; } Tab tab = format.tab[tabi]; if(tab.pos & TAB_RIGHTPOS) tab.pos = cx - (tab.pos & ~TAB_RIGHTPOS); return tab; } struct RichPara::StorePart { wchar *t; int *w; int *p; const CharFormat **f; HeightInfo *h; int pos; Font font; void Store(Lines& lines, const Part& p, int pinc); }; void RichPara::StorePart::Store(Lines& lines, const Part& part, int pinc) { CharFormat& pfmt = lines.hformat.Add(); pfmt = part.format; if(part.field && pinc) { for(int i = 0; i < part.fieldpart.GetCount(); i++) Store(lines, part.fieldpart[i], 0); pos++; } else if(part.object) { *f++ = &pfmt; Size sz = part.object.GetSize(); *w++ = sz.cx; h->ydelta = part.object.GetYDelta(); h->ascent = sz.cy - h->ydelta; h->descent = max(h->ydelta, 0); h->external = 0; lines.object.Add(part.object); h->object = &lines.object.Top(); h++; *t++ = 'x'; *p++ = pos; pos += pinc; } else { const wchar *s = part.text; const wchar *lim = part.text.End(); Font fnt = part.format; Font fi = fnt; Font wf = fnt; if(part.format.sscript) { fnt.Height(fnt.GetHeight() * 3 / 5); wf = fnt; } if(part.format.capitals) { CharFormat& cfmt = lines.hformat.Add(); cfmt = part.format; cfmt.Height(cfmt.GetHeight() * 4 / 5); Font cwf = cfmt; if(part.format.sscript) { Font fnt = cfmt; fnt.Height(fnt.GetHeight() * 3 / 5); cwf = fnt; } while(s < lim) { wchar c = *s++; if(c == 9) { *f++ = &pfmt; h->ascent = font.GetAscent(); h->descent = font.GetDescent(); h->external = font.GetExternal(); *w++ = 0; } else if(IsLower(c)) { *f++ = &cfmt; c = (wchar)ToUpper(c); h->ascent = cfmt.GetAscent(); h->descent = cfmt.GetDescent(); h->external = cfmt.GetExternal(); *w++ = c >= 32 ? cwf[c] : 0; } else { *f++ = &pfmt; h->ascent = fi.GetAscent(); h->descent = fi.GetDescent(); h->external = fi.GetExternal(); *w++ = c >= 32 ? wf[c] : 0; } h->object = NULL; *t++ = c; *p++ = pos; pos += pinc; h++; } } else { while(s < lim) { wchar c = *s++; *f++ = &pfmt; if(c == 9) { h->ascent = font.GetAscent(); h->descent = font.GetDescent(); h->external = font.GetExternal(); } else { h->ascent = fi.GetAscent(); h->descent = fi.GetDescent(); h->external = fi.GetExternal(); } h->object = NULL; *p++ = pos; pos += pinc; h++; *w++ = c >= 32 ? wf[c] : 0; *t++ = c; } } } } static int CountChars(const Array& part) { int n = 0; for(int i = 0; i < part.GetCount(); i++) { const RichPara::Part& p = part[i]; if(p.field) n += CountChars(p.fieldpart); else n += p.GetLength(); } return n; } RichPara::Lines::Lines() { justified = false; incache = false; cacheid = 0; number_chars = 0; } Array& RichPara::Lines::Cache() { static Array x; return x; } RichPara::Lines::~Lines() { if(cacheid && line.GetCount() && !incache) { Mutex::Lock __(cache_lock); Array& cache = Cache(); incache = true; cache.Insert(0) = pick(*this); // cache.SetCount(1); int total = 0; for(int i = 1; i < cache.GetCount(); i++) { total += cache[i].clen; if(total > 10000 || i > 64) { cache.SetCount(i); break; } } } } RichPara::Lines RichPara::FormatLines(int acx, const Number& n) const { Lines lines; if(cacheid) { Mutex::Lock __(cache_lock); Array& cache = Lines::Cache(); for(int i = 0; i < cache.GetCount(); i++) if(cache[i].cacheid == cacheid && cache[i].cx == acx) { lines = pick(cache[i]); lines.incache = false; cache.Remove(i); return lines; } } Number nn = n; nn.Next(format); WString number = nn.AsText(format).ToWString(); if(number.GetCount()) number.Cat(' '); int i; lines.cacheid = cacheid; lines.cx = acx; lines.len = GetLength(); lines.clen = CountChars(part) + number.GetCount(); lines.first_indent = lines.next_indent = format.indent; if(format.bullet == BULLET_TEXT) lines.first_indent = 0; else if(!format.bullet && !format.IsNumbered()) lines.next_indent = 0; if(lines.clen == 0) { Line& l = lines.line.Add(); l.pos = 0; l.ppos = 0; l.plen = 0; l.len = 0; l.cx = lines.first_indent; l.withtabs = false; HeightInfo dummy; Smh(lines, &dummy, lines.cx); l.ascent = format.GetAscent(); l.descent = format.GetDescent(); l.external = format.GetExternal(); return lines; } lines.text.Alloc(lines.clen); lines.width.Alloc(lines.clen); lines.pos.Alloc(lines.clen); lines.format.Alloc(lines.clen); lines.height.Alloc(lines.clen); StorePart sp; sp.t = lines.text; sp.w = lines.width; sp.p = lines.pos; sp.f = lines.format; sp.h = lines.height; sp.font = format; sp.pos = 0; if(number.GetCount()) { Part np; np.text = number; np.format = part.GetCount() ? part[0].format : format; np.format.indexentry.Clear(); np.format.link.Clear(); sp.Store(lines, np, 0); lines.number_chars = number.GetCount(); } for(i = 0; i < part.GetCount(); i++) sp.Store(lines, part[i], 1); wchar *s = lines.text; wchar *text = s; wchar *end = lines.text + lines.clen; wchar *space = NULL; int *w = lines.width; int cx = lines.first_indent; int rcx = lines.cx - format.lm - format.rm; bool withtabs = false; int scx = cx; while(s < end) { Tab t; if(*s == ' ') { space = s; scx = cx; } else { if(*s == '\t') { t = GetNextTab(cx + format.lm, rcx); space = NULL; } if(*s == '\n' || // fieldparts with newline cx + *w > rcx && s > text || *s == '\t' && (t.align == ALIGN_RIGHT ? t.pos - format.lm > rcx : t.pos - format.lm >= rcx)) { if(*s == '\n') // fieldparts with newline space = NULL; Line& l = lines.line.Add(); l.withtabs = withtabs; l.pos = (int)(text - lines.text); if(space) { l.len = (int)(space - text) + 1; l.cx = scx; text = s = space + 1; } else { l.len = (int)(s - text); l.cx = cx; text = s; } Smh(lines, lines.height, lines.cx); cx = lines.next_indent; w = text - ~lines.text + lines.width; space = NULL; rcx = lines.cx - format.lm - format.rm; withtabs = false; t = GetNextTab(cx + format.lm, acx); } } if(*s == '\t') { *s += t.fillchar; if(t.align == ALIGN_LEFT) { *w++ = t.pos - format.lm - cx; cx = t.pos - format.lm; } else { int tcx = 0; int *tw = w + 1; for(wchar *ts = s + 1; ts < end && *ts != '\t'; ts++) tcx += *tw++; int ww = t.pos - format.lm - cx - (t.align == ALIGN_RIGHT ? tcx : tcx / 2); if(ww > 0) { *w++ = ww; cx += ww; } else *w++ = 0; } withtabs = true; } else cx += *w++; s++; } Line& l = lines.line.Add(); l.withtabs = withtabs; l.pos = (int)(text - lines.text); l.len = (int)(s - text); l.cx = cx; Smh(lines, lines.height, lines.cx); for(i = 0; i < lines.line.GetCount(); i++) { Line& l = lines.line[i]; l.ppos = lines.pos[l.pos]; l.plen = (l.pos + l.len < lines.clen ? lines.pos[l.pos + l.len] : lines.len) - l.ppos; } return lines; } void RichPara::Lines::Justify(const RichPara::Format& format) { if(justified) return; justified = true; if(format.align != ALIGN_JUSTIFY) return; for(int i = 0; i < line.GetCount() - 1; i++) { const Line& li = line[i]; if(!li.withtabs && li.len) { const wchar *s = ~text + li.pos; const wchar *lim = s + li.len; while(lim - 1 > s) { if(*(lim - 1) != ' ') break; lim--; } while(s < lim) { if(*s != ' ' && *s != 160) break; s++; } const wchar *beg = s; int nspc = 0; while(s < lim) { if(*s == ' ' || *s == 160) nspc++; s++; } s = beg; if(nspc) { int q = ((cx - format.lm - format.rm - (i == 0 ? first_indent : next_indent) - li.cx) << 16) / nspc; int *w = beg - ~text + width; int prec = 0; while(s < lim) { if(*s == ' ' || *s == 160) { *w += (prec + q) >> 16; prec = (prec + q) & 0xffff; } w++; s++; } } } } } int RichPara::Lines::BodyHeight() const { int sum = 0; for(int i = 0; i < line.GetCount(); i++) sum += line[i].Sum(); return sum; } }