xref: /haiku/src/apps/haikudepot/textview/TextDocumentLayout.cpp (revision 644fa5a93845dc4a1bc155f1fd0f94ebdf0b47bc)
1 /*
2  * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 
6 #include "TextDocumentLayout.h"
7 
8 #include <new>
9 #include <stdio.h>
10 
11 #include <View.h>
12 
13 
14 TextDocumentLayout::TextDocumentLayout()
15 	:
16 	fWidth(0.0f),
17 	fLayoutValid(false),
18 
19 	fDocument(),
20 	fParagraphLayouts()
21 {
22 }
23 
24 
25 TextDocumentLayout::TextDocumentLayout(const TextDocumentRef& document)
26 	:
27 	fWidth(0.0f),
28 	fLayoutValid(false),
29 
30 	fDocument(document),
31 	fParagraphLayouts()
32 {
33 	_Init();
34 }
35 
36 
37 TextDocumentLayout::TextDocumentLayout(const TextDocumentLayout& other)
38 	:
39 	fWidth(other.fWidth),
40 	fLayoutValid(other.fLayoutValid),
41 
42 	fDocument(other.fDocument),
43 	fParagraphLayouts(other.fParagraphLayouts)
44 {
45 }
46 
47 
48 TextDocumentLayout::~TextDocumentLayout()
49 {
50 }
51 
52 
53 void
54 TextDocumentLayout::SetTextDocument(const TextDocumentRef& document)
55 {
56 	if (fDocument != document) {
57 		fDocument = document;
58 		_Init();
59 		fLayoutValid = false;
60 	}
61 }
62 
63 
64 void
65 TextDocumentLayout::Invalidate()
66 {
67 	if (fDocument.Get() != NULL)
68 		InvalidateParagraphs(0, fDocument->Paragraphs().CountItems());
69 }
70 
71 
72 void
73 TextDocumentLayout::InvalidateParagraphs(int32 start, int32 count)
74 {
75 	if (start < 0 || count == 0 || fDocument.Get() == NULL)
76 		return;
77 
78 	fLayoutValid = false;
79 
80 	const ParagraphList& paragraphs = fDocument->Paragraphs();
81 
82 	while (count > 0) {
83 		if (start >= paragraphs.CountItems())
84 			break;
85 		const Paragraph& paragraph = paragraphs.ItemAtFast(start);
86 		if (start >= fParagraphLayouts.CountItems()) {
87 			ParagraphLayoutRef layout(new(std::nothrow) ParagraphLayout(
88 				paragraph), true);
89 			if (layout.Get() == NULL
90 				|| !fParagraphLayouts.Add(ParagraphLayoutInfo(0.0f, layout))) {
91 				fprintf(stderr, "TextDocumentLayout::InvalidateParagraphs() - "
92 					"out of memory\n");
93 				return;
94 			}
95 		} else {
96 			const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(
97 				start);
98 			info.layout->SetParagraph(paragraph);
99 		}
100 
101 		start++;
102 		count--;
103 	}
104 
105 	// Remove any extra paragraph layouts
106 	while (paragraphs.CountItems() < fParagraphLayouts.CountItems())
107 		fParagraphLayouts.Remove(fParagraphLayouts.CountItems() - 1);
108 }
109 
110 
111 void
112 TextDocumentLayout::SetWidth(float width)
113 {
114 	if (fWidth != width) {
115 		fWidth = width;
116 		fLayoutValid = false;
117 	}
118 }
119 
120 
121 float
122 TextDocumentLayout::Height()
123 {
124 	_ValidateLayout();
125 
126 	float height = 0.0f;
127 
128 	if (fParagraphLayouts.CountItems() > 0) {
129 		const ParagraphLayoutInfo& lastLayout = fParagraphLayouts.LastItem();
130 		height = lastLayout.y + lastLayout.layout->Height();
131 	}
132 
133 	return height;
134 }
135 
136 
137 void
138 TextDocumentLayout::Draw(BView* view, const BPoint& offset,
139 	const BRect& updateRect)
140 {
141 	_ValidateLayout();
142 
143 	int layoutCount = fParagraphLayouts.CountItems();
144 	for (int i = 0; i < layoutCount; i++) {
145 		const ParagraphLayoutInfo& layout = fParagraphLayouts.ItemAtFast(i);
146 		BPoint location(offset.x, offset.y + layout.y);
147 		if (location.y > updateRect.bottom)
148 			break;
149 		if (location.y + layout.layout->Height() > updateRect.top)
150 			layout.layout->Draw(view, location);
151 	}
152 }
153 
154 
155 int32
156 TextDocumentLayout::LineIndexForOffset(int32 textOffset)
157 {
158 	int32 index = _ParagraphLayoutIndexForOffset(textOffset);
159 	if (index >= 0) {
160 		int32 lineIndex = 0;
161 		for (int32 i = 0; i < index; i++) {
162 			lineIndex += fParagraphLayouts.ItemAtFast(i).layout->CountLines();
163 		}
164 
165 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(index);
166 		return lineIndex + info.layout->LineIndexForOffset(textOffset);
167 	}
168 
169 	return 0;
170 }
171 
172 
173 int32
174 TextDocumentLayout::FirstOffsetOnLine(int32 lineIndex)
175 {
176 	int32 paragraphOffset;
177 	int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset);
178 	if (index >= 0) {
179 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(index);
180 		return info.layout->FirstOffsetOnLine(lineIndex) + paragraphOffset;
181 	}
182 
183 	return 0;
184 }
185 
186 
187 int32
188 TextDocumentLayout::LastOffsetOnLine(int32 lineIndex)
189 {
190 	int32 paragraphOffset;
191 	int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset);
192 	if (index >= 0) {
193 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(index);
194 		return info.layout->LastOffsetOnLine(lineIndex) + paragraphOffset;
195 	}
196 
197 	return 0;
198 }
199 
200 
201 int32
202 TextDocumentLayout::CountLines()
203 {
204 	_ValidateLayout();
205 
206 	int32 lineCount = 0;
207 
208 	int32 count = fParagraphLayouts.CountItems();
209 	for (int32 i = 0; i < count; i++) {
210 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(i);
211 		lineCount += info.layout->CountLines();
212 	}
213 
214 	return lineCount;
215 }
216 
217 
218 void
219 TextDocumentLayout::GetLineBounds(int32 lineIndex, float& x1, float& y1,
220 	float& x2, float& y2)
221 {
222 	int32 paragraphOffset;
223 	int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset);
224 	if (index >= 0) {
225 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(index);
226 		info.layout->GetLineBounds(lineIndex, x1, y1, x2, y2);
227 		y1 += info.y;
228 		y2 += info.y;
229 		return;
230 	}
231 
232 	x1 = 0.0f;
233 	y1 = 0.0f;
234 	x2 = 0.0f;
235 	y2 = 0.0f;
236 }
237 
238 
239 void
240 TextDocumentLayout::GetTextBounds(int32 textOffset, float& x1, float& y1,
241 	float& x2, float& y2)
242 {
243 	int32 index = _ParagraphLayoutIndexForOffset(textOffset);
244 	if (index >= 0) {
245 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(index);
246 		info.layout->GetTextBounds(textOffset, x1, y1, x2, y2);
247 		y1 += info.y;
248 		y2 += info.y;
249 		return;
250 	}
251 
252 	x1 = 0.0f;
253 	y1 = 0.0f;
254 	x2 = 0.0f;
255 	y2 = 0.0f;
256 }
257 
258 
259 int32
260 TextDocumentLayout::TextOffsetAt(float x, float y, bool& rightOfCenter)
261 {
262 	_ValidateLayout();
263 
264 	int32 textOffset = 0;
265 	rightOfCenter = false;
266 
267 	int32 paragraphs = fParagraphLayouts.CountItems();
268 	for (int32 i = 0; i < paragraphs; i++) {
269 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(i);
270 		if (y > info.y + info.layout->Height()) {
271 			textOffset += info.layout->CountGlyphs();
272 			continue;
273 		}
274 
275 		textOffset += info.layout->TextOffsetAt(x, y - info.y, rightOfCenter);
276 		break;
277 	}
278 
279 	return textOffset;
280 }
281 
282 // #pragma mark - private
283 
284 
285 void
286 TextDocumentLayout::_ValidateLayout()
287 {
288 	if (!fLayoutValid) {
289 		_Layout();
290 		fLayoutValid = true;
291 	}
292 }
293 
294 
295 void
296 TextDocumentLayout::_Init()
297 {
298 	fParagraphLayouts.Clear();
299 
300 	const ParagraphList& paragraphs = fDocument->Paragraphs();
301 
302 	int paragraphCount = paragraphs.CountItems();
303 	for (int i = 0; i < paragraphCount; i++) {
304 		const Paragraph& paragraph = paragraphs.ItemAtFast(i);
305 		ParagraphLayoutRef layout(new(std::nothrow) ParagraphLayout(paragraph),
306 			true);
307 		if (layout.Get() == NULL
308 			|| !fParagraphLayouts.Add(ParagraphLayoutInfo(0.0f, layout))) {
309 			fprintf(stderr, "TextDocumentLayout::_Layout() - out of memory\n");
310 			return;
311 		}
312 	}
313 }
314 
315 
316 void
317 TextDocumentLayout::_Layout()
318 {
319 	float y = 0.0f;
320 
321 	int layoutCount = fParagraphLayouts.CountItems();
322 	for (int i = 0; i < layoutCount; i++) {
323 		ParagraphLayoutInfo info = fParagraphLayouts.ItemAtFast(i);
324 		const ParagraphStyle& style = info.layout->Style();
325 
326 		if (i > 0)
327 			y += style.SpacingTop();
328 
329 		fParagraphLayouts.Replace(i, ParagraphLayoutInfo(y, info.layout));
330 
331 		info.layout->SetWidth(fWidth);
332 		y += info.layout->Height() + style.SpacingBottom();
333 	}
334 }
335 
336 
337 int32
338 TextDocumentLayout::_ParagraphLayoutIndexForOffset(int32& textOffset)
339 {
340 	_ValidateLayout();
341 
342 	int32 paragraphs = fParagraphLayouts.CountItems();
343 	for (int32 i = 0; i < paragraphs - 1; i++) {
344 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(i);
345 
346 		int32 length = info.layout->CountGlyphs();
347 		if (textOffset >= length) {
348 			textOffset -= length;
349 			continue;
350 		}
351 
352 		return i;
353 	}
354 
355 	if (paragraphs > 0) {
356 		const ParagraphLayoutInfo& info = fParagraphLayouts.LastItem();
357 
358 		// Return last paragraph if the textOffset is still within or
359 		// exactly behind the last valid offset in that paragraph.
360 		int32 length = info.layout->CountGlyphs();
361 		if (textOffset <= length)
362 			return paragraphs - 1;
363 	}
364 
365 	return -1;
366 }
367 
368 int32
369 TextDocumentLayout::_ParagraphLayoutIndexForLineIndex(int32& lineIndex,
370 	int32& paragraphOffset)
371 {
372 	_ValidateLayout();
373 
374 	paragraphOffset = 0;
375 	int32 paragraphs = fParagraphLayouts.CountItems();
376 	for (int32 i = 0; i < paragraphs; i++) {
377 		const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(i);
378 
379 		int32 lineCount = info.layout->CountLines();
380 		if (lineIndex >= lineCount) {
381 			lineIndex -= lineCount;
382 			paragraphOffset += info.layout->CountGlyphs();
383 			continue;
384 		}
385 
386 		return i;
387 	}
388 
389 	return -1;
390 }
391