xref: /haiku/src/apps/haikudepot/textview/Paragraph.cpp (revision 3634f142352af2428aed187781fc9d75075e9140)
1 /*
2  * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
3  * Copyright 2021, Andrew Lindesay <apl@lindesay.co.nz>.
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 
7 #include "Paragraph.h"
8 
9 #include <algorithm>
10 #include <stdio.h>
11 
12 
13 Paragraph::Paragraph()
14 	:
15 	fStyle()
16 {
17 }
18 
19 
20 Paragraph::Paragraph(const ParagraphStyle& style)
21 	:
22 	fStyle(style),
23 	fTextSpans(),
24 	fCachedLength(-1)
25 {
26 }
27 
28 
29 Paragraph::Paragraph(const Paragraph& other)
30 	:
31 	fStyle(other.fStyle),
32 	fTextSpans(other.fTextSpans),
33 	fCachedLength(other.fCachedLength)
34 {
35 }
36 
37 
38 Paragraph&
39 Paragraph::operator=(const Paragraph& other)
40 {
41 	fStyle = other.fStyle;
42 	fTextSpans = other.fTextSpans;
43 	fCachedLength = other.fCachedLength;
44 
45 	return *this;
46 }
47 
48 
49 bool
50 Paragraph::operator==(const Paragraph& other) const
51 {
52 	if (this == &other)
53 		return true;
54 
55 	return fStyle == other.fStyle
56 		&& fTextSpans == other.fTextSpans;
57 }
58 
59 
60 bool
61 Paragraph::operator!=(const Paragraph& other) const
62 {
63 	return !(*this == other);
64 }
65 
66 
67 int32
68 Paragraph::CountTextSpans() const
69 {
70 	return static_cast<int32>(fTextSpans.size());
71 }
72 
73 
74 const TextSpan&
75 Paragraph::TextSpanAtIndex(int32 index) const
76 {
77 	return fTextSpans[index];
78 }
79 
80 
81 void
82 Paragraph::SetStyle(const ParagraphStyle& style)
83 {
84 	fStyle = style;
85 }
86 
87 
88 bool
89 Paragraph::Prepend(const TextSpan& span)
90 {
91 	_InvalidateCachedLength();
92 
93 	// Try to merge with first span if the TextStyles are equal
94 	if (!fTextSpans.empty()) {
95 		const TextSpan& firstSpan = fTextSpans[0];
96 		if (firstSpan.Style() == span.Style()) {
97 			BString text(span.Text());
98 			text.Append(firstSpan.Text());
99 			fTextSpans[0] = TextSpan(text, span.Style());
100 			return true;
101 		}
102 	}
103 	fTextSpans.push_back(span);
104 	return true;
105 }
106 
107 
108 bool
109 Paragraph::Append(const TextSpan& span)
110 {
111 	_InvalidateCachedLength();
112 
113 	// Try to merge with last span if the TextStyles are equal
114 	if (!fTextSpans.empty()) {
115 		const TextSpan& lastSpan = fTextSpans[fTextSpans.size() - 1];
116 		if (lastSpan.Style() == span.Style()) {
117 			BString text(lastSpan.Text());
118 			text.Append(span.Text());
119 			fTextSpans.pop_back();
120 			fTextSpans.push_back(TextSpan(text, span.Style()));
121 			return true;
122 		}
123 	}
124 	fTextSpans.push_back(span);
125 	return true;
126 }
127 
128 
129 bool
130 Paragraph::Insert(int32 offset, const TextSpan& newSpan)
131 {
132 	_InvalidateCachedLength();
133 
134 	int32 index = 0;
135 
136 	{
137 		int32 countTextSpans = static_cast<int32>(fTextSpans.size());
138 		while (index < countTextSpans) {
139 			const TextSpan& span = fTextSpans[index];
140 			if (offset - span.CountChars() < 0)
141 				break;
142 			offset -= span.CountChars();
143 			index++;
144 		}
145 
146 		if (countTextSpans == index)
147 			return Append(newSpan);
148 	}
149 
150 	// Try to merge with span at index if the TextStyles are equal
151 	TextSpan span = fTextSpans[index];
152 	if (span.Style() == newSpan.Style()) {
153 		span.Insert(offset, newSpan.Text());
154 		fTextSpans[index] = span;
155 		return true;
156 	}
157 
158 	if (offset == 0) {
159 		if (index > 0) {
160 			// Try to merge with TextSpan before if offset == 0 && index > 0
161 			TextSpan span = fTextSpans[index - 1];
162 			if (span.Style() == newSpan.Style()) {
163 				span.Insert(span.CountChars(), newSpan.Text());
164 				fTextSpans[index - 1] = span;
165 				return true;
166 			}
167 		}
168 		// Just insert the new span before the one at index
169 		fTextSpans.insert(fTextSpans.begin() + index, newSpan);
170 		return true;
171 	}
172 
173 	// Split the span,
174 	TextSpan spanBefore = span.SubSpan(0, offset);
175 	TextSpan spanAfter = span.SubSpan(offset, span.CountChars() - offset);
176 
177 	fTextSpans[index] = spanBefore;
178 	fTextSpans.insert(fTextSpans.begin() + (index + 1), newSpan);
179 	fTextSpans.insert(fTextSpans.begin() + (index + 2), spanAfter);
180 	return true;
181 }
182 
183 
184 bool
185 Paragraph::Remove(int32 offset, int32 length)
186 {
187 	if (length == 0)
188 		return true;
189 
190 	_InvalidateCachedLength();
191 
192 	int32 index = 0;
193 
194 	{
195 		int32 countTextSpans = static_cast<int32>(fTextSpans.size());
196 		while (index < countTextSpans) {
197 			const TextSpan& span = fTextSpans[index];
198 			if (offset - span.CountChars() < 0)
199 				break;
200 			offset -= span.CountChars();
201 			index++;
202 		}
203 
204 		if (index >= countTextSpans)
205 			return false;
206 	}
207 
208 	TextSpan span(fTextSpans[index]);
209 	int32 removeLength = std::min(span.CountChars() - offset, length);
210 	span.Remove(offset, removeLength);
211 	length -= removeLength;
212 	index += 1;
213 
214 	// Remove more spans if necessary
215 	while (length > 0 && index < static_cast<int32>(fTextSpans.size())) {
216 		int32 spanLength = fTextSpans[index].CountChars();
217 		if (spanLength <= length) {
218 			fTextSpans.erase(fTextSpans.begin() + index);
219 			length -= spanLength;
220 		} else {
221 			// Reached last span
222 			removeLength = std::min(length, spanLength);
223 			TextSpan lastSpan = fTextSpans[index].SubSpan(
224 				removeLength, spanLength - removeLength);
225 			// Try to merge with first span, otherwise replace span at index
226 			if (lastSpan.Style() == span.Style()) {
227 				span.Insert(span.CountChars(), lastSpan.Text());
228 				fTextSpans.erase(fTextSpans.begin() + index);
229 			} else {
230 				fTextSpans[index] = lastSpan;
231 			}
232 
233 			break;
234 		}
235 	}
236 
237 	// See if anything from the TextSpan at offset remained, keep it as empty
238 	// span if it is the last remaining span.
239 	index--;
240 	if (span.CountChars() > 0 || static_cast<int32>(fTextSpans.size()) == 1) {
241 		fTextSpans[index] = span;
242 	} else {
243 		fTextSpans.erase(fTextSpans.begin() + index);
244 		index--;
245 	}
246 
247 	// See if spans can be merged after one has been removed.
248 	if (index >= 0 && index + 1 < static_cast<int32>(fTextSpans.size())) {
249 		const TextSpan& span1 = fTextSpans[index];
250 		const TextSpan& span2 = fTextSpans[index + 1];
251 		if (span1.Style() == span2.Style()) {
252 			span = span1;
253 			span.Append(span2.Text());
254 			fTextSpans[index] = span;
255 			fTextSpans.erase(fTextSpans.begin() + (index + 1));
256 		}
257 	}
258 
259 	return true;
260 }
261 
262 
263 void
264 Paragraph::Clear()
265 {
266 	fTextSpans.clear();
267 }
268 
269 
270 int32
271 Paragraph::Length() const
272 {
273 	if (fCachedLength >= 0)
274 		return fCachedLength;
275 
276 	int32 length = 0;
277 	std::vector<TextSpan>::const_iterator it;
278 	for (it = fTextSpans.begin(); it != fTextSpans.end(); it++) {
279 		const TextSpan& span = *it;
280 		length += span.CountChars();
281 	}
282 
283 	fCachedLength = length;
284 	return length;
285 }
286 
287 
288 bool
289 Paragraph::IsEmpty() const
290 {
291 	return fTextSpans.empty();
292 }
293 
294 
295 bool
296 Paragraph::EndsWith(BString string) const
297 {
298 	int length = Length();
299 	int endLength = string.CountChars();
300 	int start = length - endLength;
301 	BString end = Text(start, endLength);
302 	return end == string;
303 }
304 
305 
306 BString
307 Paragraph::Text() const
308 {
309 	BString result;
310 	std::vector<TextSpan>::const_iterator it;
311 	for (it = fTextSpans.begin(); it != fTextSpans.end(); it++) {
312 		const TextSpan& span = *it;
313 		result << span.Text();
314 	}
315 	return result;
316 }
317 
318 
319 BString
320 Paragraph::Text(int32 start, int32 length) const
321 {
322 	Paragraph subParagraph = SubParagraph(start, length);
323 	return subParagraph.Text();
324 }
325 
326 
327 Paragraph
328 Paragraph::SubParagraph(int32 start, int32 length) const
329 {
330 	if (start < 0)
331 		start = 0;
332 
333 	if (start == 0 && length == Length())
334 		return *this;
335 
336 	Paragraph result(fStyle);
337 
338 	std::vector<TextSpan>::const_iterator it;
339 	for (it = fTextSpans.begin(); it != fTextSpans.end(); it++) {
340 		const TextSpan& span = *it;
341 		int32 spanLength = span.CountChars();
342 		if (spanLength == 0)
343 			continue;
344 		if (start > spanLength) {
345 			// Skip span if its before start
346 			start -= spanLength;
347 			continue;
348 		}
349 
350 		// Remaining span length after start
351 		spanLength -= start;
352 		int32 copyLength = std::min(spanLength, length);
353 
354 		if (start == 0 && length == spanLength)
355 			result.Append(span);
356 		else
357 			result.Append(span.SubSpan(start, copyLength));
358 
359 		length -= copyLength;
360 		if (length == 0)
361 			break;
362 
363 		// Next span is copied from its beginning
364 		start = 0;
365 	}
366 
367 	return result;
368 }
369 
370 
371 void
372 Paragraph::PrintToStream() const
373 {
374 	int32 spanCount = static_cast<int32>(fTextSpans.size());
375 	if (spanCount == 0) {
376 		printf("  <p/>\n");
377 		return;
378 	}
379 	printf("  <p>\n");
380 	for (int32 i = 0; i < spanCount; i++) {
381 		const TextSpan& span = fTextSpans[i];
382 		if (span.CountChars() == 0)
383 			printf("    <span/>\n");
384 		else {
385 			BString text = span.Text();
386 			text.ReplaceAll("\n", "\\n");
387 			printf("    <span>%s</span>\n", text.String());
388 		}
389 	}
390 	printf("  </p>\n");
391 }
392 
393 
394 // #pragma mark -
395 
396 
397 void
398 Paragraph::_InvalidateCachedLength()
399 {
400 	fCachedLength = -1;
401 }
402