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