1 #include "RichText.h"
2 
3 namespace Upp {
4 
Smh(Lines & lines,HeightInfo * th,int cx) const5 void RichPara::Smh(Lines& lines, HeightInfo *th, int cx) const
6 {
7 	Line& l = lines.line.Top();
8 	l.ascent = l.descent = l.external = 0;
9 	const HeightInfo *he = th + l.pos + l.len;
10 	for(const HeightInfo *h = th + l.pos; h < he; h++) {
11 		if(h->ascent > l.ascent) l.ascent = h->ascent;
12 		if(h->descent > l.descent) l.descent = h->descent;
13 		if(h->external > l.external) l.external = h->external;
14 	}
15 	if(format.linespacing == LSP15) {
16 		l.ascent = (3 * l.ascent) >> 1;
17 		l.descent = (3 * l.descent) >> 1;
18 	}
19 	if(format.linespacing == LSP20) {
20 		l.ascent = 2 * l.ascent;
21 		l.descent = 2 * l.descent;
22 	}
23 	l.xpos = format.lm;
24 	cx -= format.lm + format.rm;
25 	l.xpos += lines.GetCount() == 1 ? lines.first_indent : lines.next_indent;
26 	if(!l.withtabs)
27 		switch(format.align) {
28 		case ALIGN_RIGHT:
29 			l.xpos += cx - l.cx;
30 			break;
31 		case ALIGN_CENTER:
32 			l.xpos += (cx - l.cx) / 2;
33 			break;
34 		}
35 	l.cx -= lines.GetCount() == 1 ? lines.first_indent : lines.next_indent;
36 }
37 
GetNextTab(int pos,int cx) const38 RichPara::Tab RichPara::GetNextTab(int pos, int cx) const
39 {
40 	int tabi = -1;
41 	int dist = INT_MAX;
42 	for(int i = 0; i < format.tab.GetCount(); i++) {
43 		const Tab& tab = format.tab[i];
44 		int tabpos = tab.pos;
45 		if(tabpos & TAB_RIGHTPOS)
46 			tabpos = cx - (tabpos & ~TAB_RIGHTPOS);
47 		if(tabpos > pos && tabpos - pos < dist) {
48 			tabi = i;
49 			dist = tabpos - pos;
50 		}
51 	}
52 	if(format.bullet == BULLET_TEXT) {
53 		int q = format.indent + format.lm;
54 		if(q > pos && q - pos < dist) {
55 			Tab tab;
56 			tab.align = ALIGN_LEFT;
57 			tab.pos = q;
58 			return tab;
59 		}
60 	}
61 	if(tabi < 0) {
62 		Tab tab;
63 		tab.pos = format.tabsize ? (pos + format.tabsize) / format.tabsize * format.tabsize : 0;
64 		tab.align = ALIGN_LEFT;
65 		return tab;
66 	}
67 	Tab tab = format.tab[tabi];
68 	if(tab.pos & TAB_RIGHTPOS)
69 		tab.pos = cx - (tab.pos & ~TAB_RIGHTPOS);
70 	return tab;
71 }
72 
73 struct RichPara::StorePart {
74 	wchar             *t;
75 	int               *w;
76 	int               *p;
77 	const CharFormat **f;
78 	HeightInfo        *h;
79 	int                pos;
80 	FontInfo           pfi;
81 
82 	void Store(Lines& lines, const Part& p, int pinc);
83 };
84 
Store(Lines & lines,const Part & part,int pinc)85 void RichPara::StorePart::Store(Lines& lines, const Part& part, int pinc)
86 {
87 	CharFormat& pfmt = lines.hformat.Add();
88 	pfmt = part.format;
89 	if(part.field && pinc) {
90 		for(int i = 0; i < part.fieldpart.GetCount(); i++)
91 			Store(lines, part.fieldpart[i], 0);
92 		pos++;
93 	}
94 	else
95 	if(part.object) {
96 		*f++ = &pfmt;
97 		Size sz = part.object.GetSize();
98 		*w++ = sz.cx;
99 		h->ydelta = part.object.GetYDelta();
100 		h->ascent = sz.cy - h->ydelta;
101 		h->descent = max(h->ydelta, 0);
102 		h->external = 0;
103 		lines.object.Add(part.object);
104 		h->object = &lines.object.Top();
105 		h++;
106 		*t++ = 'x';
107 		*p++ = pos;
108 		pos += pinc;
109 	}
110 	else {
111 		const wchar *s = part.text;
112 		const wchar *lim = part.text.End();
113 		Font fnt = part.format;
114 		FontInfo fi = fnt.Info();
115 		FontInfo wfi = fi;
116 		if(part.format.sscript) {
117 			fnt.Height(fnt.GetHeight() * 3 / 5);
118 			wfi = fnt.Info();
119 		}
120 		if(part.format.capitals) {
121 			CharFormat& cfmt = lines.hformat.Add();
122 			cfmt = part.format;
123 			cfmt.Height(cfmt.GetHeight() * 4 / 5);
124 			FontInfo cfi = cfmt.Info();
125 			FontInfo cwfi = cfi;
126 			if(part.format.sscript) {
127 				Font fnt = cfmt;
128 				fnt.Height(fnt.GetHeight() * 3 / 5);
129 				cwfi = fnt.Info();
130 			}
131 
132 			while(s < lim) {
133 				wchar c = *s++;
134 				if(c == 9) {
135 					*f++ = &pfmt;
136 					h->ascent = pfi.GetAscent();
137 					h->descent = pfi.GetDescent();
138 					h->external = pfi.GetExternal();
139 					*w++ = 0;
140 				}
141 				else
142 				if(IsLower(c)) {
143 					*f++ = &cfmt;
144 					c = (wchar)ToUpper(c);
145 					h->ascent = cfi.GetAscent();
146 					h->descent = cfi.GetDescent();
147 					h->external = cfi.GetExternal();
148 					*w++ = c >= 32 ? cwfi[c] : 0;
149 				}
150 				else {
151 					*f++ = &pfmt;
152 					h->ascent = fi.GetAscent();
153 					h->descent = fi.GetDescent();
154 					h->external = fi.GetExternal();
155 					*w++ = c >= 32 ? wfi[c] : 0;
156 				}
157 				h->object = NULL;
158 				*t++ = c;
159 				*p++ = pos;
160 				pos += pinc;
161 				h++;
162 			}
163 		}
164 		else {
165 			while(s < lim) {
166 				wchar c = *s++;
167 				*f++ = &pfmt;
168 				if(c == 9) {
169 					h->ascent = pfi.GetAscent();
170 					h->descent = pfi.GetDescent();
171 					h->external = pfi.GetExternal();
172 				}
173 				else {
174 					h->ascent = fi.GetAscent();
175 					h->descent = fi.GetDescent();
176 					h->external = fi.GetExternal();
177 				}
178 				h->object = NULL;
179 				*p++ = pos;
180 				pos += pinc;
181 				h++;
182 				*w++ = c >= 32 ? wfi[c] : 0;
183 				*t++ = c;
184 			}
185 		}
186 	}
187 }
188 
CountChars(const Array<RichPara::Part> & part)189 static int CountChars(const Array<RichPara::Part>& part)
190 {
191 	int n = 0;
192 	for(int i = 0; i < part.GetCount(); i++) {
193 		const RichPara::Part& p = part[i];
194 		if(p.field)
195 			n += CountChars(p.fieldpart);
196 		else
197 			n += p.GetLength();
198 	}
199 	return n;
200 }
201 
Lines()202 RichPara::Lines::Lines()
203 {
204 	justified = false;
205 	incache = false;
206 	cacheid = 0;
207 }
208 
Cache()209 Array<RichPara::Lines>& RichPara::Lines::Cache()
210 {
211 	static Array<Lines> x;
212 	return x;
213 }
214 
~Lines()215 RichPara::Lines::~Lines()
216 {
217 	if(cacheid && line.GetCount() && !incache) {
218 		Mutex::Lock __(cache_lock);
219 		Array<Lines>& cache = Cache();
220 		incache = true;
221 		cache.Insert(0) = pick(*this);
222 //		cache.SetCount(1);
223 		int total = 0;
224 		for(int i = 1; i < cache.GetCount(); i++) {
225 			total += cache[i].clen;
226 			if(total > 10000 || i > 64) {
227 				cache.SetCount(i);
228 				break;
229 			}
230 		}
231 	}
232 }
233 
FormatLines(int acx) const234 RichPara::Lines RichPara::FormatLines(int acx) const
235 {
236 	Lines lines;
237 	if(cacheid) {
238 		Mutex::Lock __(cache_lock);
239 		Array<Lines>& cache = Lines::Cache();
240 		for(int i = 0; i < cache.GetCount(); i++)
241 			if(cache[i].cacheid == cacheid && cache[i].cx == acx) {
242 				lines = pick(cache[i]);
243 				lines.incache = false;
244 				cache.Remove(i);
245 				return lines;
246 			}
247 	}
248 
249 	int i;
250 	lines.cacheid = cacheid;
251 	lines.cx = acx;
252 	lines.len = GetLength();
253 	lines.clen = CountChars(part);
254 	lines.first_indent = lines.next_indent = format.indent;
255 	if(format.bullet == BULLET_TEXT)
256 		lines.first_indent = 0;
257 	else
258 	if(!format.bullet && !format.IsNumbered())
259 		lines.next_indent = 0;
260 
261 	FontInfo pfi = format.Info();
262 	if(lines.len == 0) {
263 		Line& l = lines.line.Add();
264 		l.pos = 0;
265 		l.ppos = 0;
266 		l.plen = 0;
267 		l.len = 0;
268 		l.cx = lines.first_indent;
269 		l.withtabs = false;
270 		HeightInfo dummy;
271 		Smh(lines, &dummy, lines.cx);
272 		l.ascent = pfi.GetAscent();
273 		l.descent = pfi.GetDescent();
274 		l.external = pfi.GetExternal();
275 		return lines;
276 	}
277 
278 	lines.text.Alloc(lines.clen);
279 	lines.width.Alloc(lines.clen);
280 	lines.pos.Alloc(lines.clen);
281 	lines.format.Alloc(lines.clen);
282 	lines.height.Alloc(lines.clen);
283 
284 	StorePart sp;
285 	sp.t = lines.text;
286 	sp.w = lines.width;
287 	sp.p = lines.pos;
288 	sp.f = lines.format;
289 	sp.h = lines.height;
290 	sp.pfi = pfi;
291 	sp.pos = 0;
292 
293 	for(i = 0; i < part.GetCount(); i++)
294 		sp.Store(lines, part[i], 1);
295 
296 	wchar *s = lines.text;
297 	wchar *text = s;
298 	wchar *end = lines.text + lines.clen;
299 	wchar *space = NULL;
300 	int *w = lines.width;
301 	int cx = lines.first_indent;
302 	int rcx = lines.cx - format.lm - format.rm;
303 	bool withtabs = false;
304 	int scx = cx;
305 	while(s < end) {
306 		Tab t;
307 		if(*s == ' ') {
308 			space = s;
309 			scx = cx;
310 		}
311 		else {
312 			if(*s == '\t') {
313 				t = GetNextTab(cx + format.lm, rcx);
314 				space = NULL;
315 			}
316 			if(cx + *w > rcx && s > text ||
317 			   *s == '\t' && (t.align == ALIGN_RIGHT ? t.pos - format.lm > rcx
318 			                                         : t.pos - format.lm >= rcx)) {
319 				Line& l = lines.line.Add();
320 				l.withtabs = withtabs;
321 				l.pos = (int)(text - lines.text);
322 				if(space) {
323 					l.len = (int)(space - text) + 1;
324 					l.cx = scx;
325 					text = s = space + 1;
326 				}
327 				else {
328 					l.len = (int)(s - text);
329 					l.cx = cx;
330 					text = s;
331 				}
332 				Smh(lines, lines.height, lines.cx);
333 				cx = lines.next_indent;
334 				w = text - ~lines.text + lines.width;
335 				space = NULL;
336 				rcx = lines.cx - format.lm - format.rm;
337 				withtabs = false;
338 				t = GetNextTab(cx + format.lm, acx);
339 			}
340 		}
341 		if(*s == '\t') {
342 			*s += t.fillchar;
343 			if(t.align == ALIGN_LEFT) {
344 				*w++ = t.pos - format.lm - cx;
345 				cx = t.pos - format.lm;
346 			}
347 			else {
348 				int tcx = 0;
349 				int *tw = w + 1;
350 				for(wchar *ts = s + 1; ts < end && *ts != '\t'; ts++)
351 					tcx += *tw++;
352 				int ww = t.pos - format.lm - cx - (t.align == ALIGN_RIGHT ? tcx : tcx / 2);
353 				if(ww > 0) {
354 					*w++ = ww;
355 					cx += ww;
356 				}
357 				else
358 					*w++ = 0;
359 			}
360 			withtabs = true;
361 		}
362 		else
363 			cx += *w++;
364 		s++;
365 	}
366 	Line& l = lines.line.Add();
367 	l.withtabs = withtabs;
368 	l.pos = (int)(text - lines.text);
369 	l.len = (int)(s - text);
370 	l.cx = cx;
371 	Smh(lines, lines.height, lines.cx);
372 	for(i = 0; i < lines.line.GetCount(); i++) {
373 		Line& l = lines.line[i];
374 		l.ppos = lines.pos[l.pos];
375 		l.plen = (l.pos + l.len < lines.clen ? lines.pos[l.pos + l.len] : lines.len) - l.ppos;
376 	}
377 
378 	return lines;
379 }
380 
Justify(const RichPara::Format & format)381 void RichPara::Lines::Justify(const RichPara::Format& format)
382 {
383 	if(justified)
384 		return;
385 	justified = true;
386 	if(format.align != ALIGN_JUSTIFY) return;
387 	for(int i = 0; i < line.GetCount() - 1; i++) {
388 		const Line& li = line[i];
389 		if(!li.withtabs && li.len) {
390 			const wchar *s = ~text + li.pos;
391 			const wchar *lim = s + li.len;
392 			while(lim - 1 > s) {
393 				if(*(lim - 1) != ' ') break;
394 				lim--;
395 			}
396 			while(s < lim) {
397 				if(*s != ' ' && *s != 160) break;
398 				s++;
399 			}
400 
401 			const wchar *beg = s;
402 			int nspc = 0;
403 			while(s < lim) {
404 				if(*s == ' ' || *s == 160) nspc++;
405 				s++;
406 			}
407 			s = beg;
408 			if(nspc) {
409 				int q = ((cx - format.lm - format.rm -
410 				          (i == 0 ? first_indent : next_indent) - li.cx) << 16)
411 				        / nspc;
412 				int *w = beg - ~text + width;
413 				int prec = 0;
414 				while(s < lim) {
415 					if(*s == ' ' || *s == 160) {
416 						*w += (prec + q) >> 16;
417 						prec = (prec + q) & 0xffff;
418 					}
419 					w++;
420 					s++;
421 				}
422 			}
423 		}
424 	}
425 }
426 
BodyHeight() const427 int RichPara::Lines::BodyHeight() const
428 {
429 	int sum = 0;
430 	for(int i = 0; i < line.GetCount(); i++)
431 		sum += line[i].Sum();
432 	return sum;
433 }
434 
435 }
436