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 12 Paragraph::Paragraph() 13 : 14 fStyle() 15 { 16 } 17 18 19 Paragraph::Paragraph(const ParagraphStyle& style) 20 : 21 fStyle(style), 22 fTextSpans(), 23 fCachedLength(-1) 24 { 25 } 26 27 28 Paragraph::Paragraph(const Paragraph& other) 29 : 30 fStyle(other.fStyle), 31 fTextSpans(other.fTextSpans), 32 fCachedLength(other.fCachedLength) 33 { 34 } 35 36 37 Paragraph& 38 Paragraph::operator=(const Paragraph& other) 39 { 40 fStyle = other.fStyle; 41 fTextSpans = other.fTextSpans; 42 fCachedLength = other.fCachedLength; 43 44 return *this; 45 } 46 47 48 bool 49 Paragraph::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 59 bool 60 Paragraph::operator!=(const Paragraph& other) const 61 { 62 return !(*this == other); 63 } 64 65 66 void 67 Paragraph::SetStyle(const ParagraphStyle& style) 68 { 69 fStyle = style; 70 } 71 72 73 bool 74 Paragraph::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 91 bool 92 Paragraph::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 110 bool 111 Paragraph::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 157 bool 158 Paragraph::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 232 void 233 Paragraph::Clear() 234 { 235 fTextSpans.Clear(); 236 } 237 238 239 int32 240 Paragraph::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 256 bool 257 Paragraph::IsEmpty() const 258 { 259 return fTextSpans.CountItems() == 0; 260 } 261 262 263 bool 264 Paragraph::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 274 BString 275 Paragraph::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 287 BString 288 Paragraph::Text(int32 start, int32 length) const 289 { 290 Paragraph subParagraph = SubParagraph(start, length); 291 return subParagraph.Text(); 292 } 293 294 295 Paragraph 296 Paragraph::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 339 void 340 Paragraph::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 365 void 366 Paragraph::_InvalidateCachedLength() 367 { 368 fCachedLength = -1; 369 } 370