1 /* 2 * Copyright 2013-2015, 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 class LayoutTextListener : public TextListener { 15 public: 16 LayoutTextListener(TextDocumentLayout* layout) 17 : 18 fLayout(layout) 19 { 20 } 21 22 virtual void TextChanging(TextChangingEvent& event) 23 { 24 } 25 26 virtual void TextChanged(const TextChangedEvent& event) 27 { 28 // printf("TextChanged(%" B_PRIi32 ", %" B_PRIi32 ")\n", 29 // event.FirstChangedParagraph(), 30 // event.ChangedParagraphCount()); 31 // TODO: The event does not contain useful data. Make the event 32 // system work so only the affected paragraphs are updated. 33 // I think we need "first affected", "last affected" (both relative 34 // to the original paragraph count), and than how many new paragraphs 35 // are between these. From the difference in old number of paragraphs 36 // inbetween and the new count, we know how many new paragraphs are 37 // missing, and the rest in the range needs to be updated. 38 // fLayout->InvalidateParagraphs(event.FirstChangedParagraph(), 39 // event.ChangedParagraphCount()); 40 fLayout->Invalidate(); 41 } 42 43 private: 44 TextDocumentLayout* fLayout; 45 }; 46 47 48 49 TextDocumentLayout::TextDocumentLayout() 50 : 51 fWidth(0.0f), 52 fLayoutValid(false), 53 54 fDocument(), 55 fTextListener(new(std::nothrow) LayoutTextListener(this), true), 56 fParagraphLayouts() 57 { 58 } 59 60 61 TextDocumentLayout::TextDocumentLayout(const TextDocumentRef& document) 62 : 63 fWidth(0.0f), 64 fLayoutValid(false), 65 66 fDocument(), 67 fTextListener(new(std::nothrow) LayoutTextListener(this), true), 68 fParagraphLayouts() 69 { 70 SetTextDocument(document); 71 } 72 73 74 TextDocumentLayout::TextDocumentLayout(const TextDocumentLayout& other) 75 : 76 fWidth(other.fWidth), 77 fLayoutValid(other.fLayoutValid), 78 79 fDocument(other.fDocument), 80 fTextListener(new(std::nothrow) LayoutTextListener(this), true), 81 fParagraphLayouts(other.fParagraphLayouts) 82 { 83 if (fDocument.Get() != NULL) 84 fDocument->AddListener(fTextListener); 85 } 86 87 88 TextDocumentLayout::~TextDocumentLayout() 89 { 90 SetTextDocument(NULL); 91 } 92 93 94 void 95 TextDocumentLayout::SetTextDocument(const TextDocumentRef& document) 96 { 97 if (fDocument == document) 98 return; 99 100 if (fDocument.Get() != NULL) 101 fDocument->RemoveListener(fTextListener); 102 103 fDocument = document; 104 _Init(); 105 fLayoutValid = false; 106 107 if (fDocument.Get() != NULL) 108 fDocument->AddListener(fTextListener); 109 } 110 111 112 void 113 TextDocumentLayout::Invalidate() 114 { 115 if (fDocument.Get() != NULL) 116 InvalidateParagraphs(0, fDocument->Paragraphs().CountItems()); 117 } 118 119 120 void 121 TextDocumentLayout::InvalidateParagraphs(int32 start, int32 count) 122 { 123 if (start < 0 || count == 0 || fDocument.Get() == NULL) 124 return; 125 126 fLayoutValid = false; 127 128 const ParagraphList& paragraphs = fDocument->Paragraphs(); 129 130 while (count > 0) { 131 if (start >= paragraphs.CountItems()) 132 break; 133 const Paragraph& paragraph = paragraphs.ItemAtFast(start); 134 if (start >= fParagraphLayouts.CountItems()) { 135 ParagraphLayoutRef layout(new(std::nothrow) ParagraphLayout( 136 paragraph), true); 137 if (layout.Get() == NULL 138 || !fParagraphLayouts.Add(ParagraphLayoutInfo(0.0f, layout))) { 139 fprintf(stderr, "TextDocumentLayout::InvalidateParagraphs() - " 140 "out of memory\n"); 141 return; 142 } 143 } else { 144 const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast( 145 start); 146 info.layout->SetParagraph(paragraph); 147 } 148 149 start++; 150 count--; 151 } 152 153 // Remove any extra paragraph layouts 154 while (paragraphs.CountItems() < fParagraphLayouts.CountItems()) 155 fParagraphLayouts.Remove(fParagraphLayouts.CountItems() - 1); 156 } 157 158 159 void 160 TextDocumentLayout::SetWidth(float width) 161 { 162 if (fWidth != width) { 163 fWidth = width; 164 fLayoutValid = false; 165 } 166 } 167 168 169 float 170 TextDocumentLayout::Height() 171 { 172 _ValidateLayout(); 173 174 float height = 0.0f; 175 176 if (fParagraphLayouts.CountItems() > 0) { 177 const ParagraphLayoutInfo& lastLayout = fParagraphLayouts.LastItem(); 178 height = lastLayout.y + lastLayout.layout->Height(); 179 } 180 181 return height; 182 } 183 184 185 void 186 TextDocumentLayout::Draw(BView* view, const BPoint& offset, 187 const BRect& updateRect) 188 { 189 _ValidateLayout(); 190 191 int layoutCount = fParagraphLayouts.CountItems(); 192 for (int i = 0; i < layoutCount; i++) { 193 const ParagraphLayoutInfo& layout = fParagraphLayouts.ItemAtFast(i); 194 BPoint location(offset.x, offset.y + layout.y); 195 if (location.y > updateRect.bottom) 196 break; 197 if (location.y + layout.layout->Height() > updateRect.top) 198 layout.layout->Draw(view, location); 199 } 200 } 201 202 203 int32 204 TextDocumentLayout::LineIndexForOffset(int32 textOffset) 205 { 206 int32 index = _ParagraphLayoutIndexForOffset(textOffset); 207 if (index >= 0) { 208 int32 lineIndex = 0; 209 for (int32 i = 0; i < index; i++) { 210 lineIndex += fParagraphLayouts.ItemAtFast(i).layout->CountLines(); 211 } 212 213 const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(index); 214 return lineIndex + info.layout->LineIndexForOffset(textOffset); 215 } 216 217 return 0; 218 } 219 220 221 int32 222 TextDocumentLayout::FirstOffsetOnLine(int32 lineIndex) 223 { 224 int32 paragraphOffset; 225 int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset); 226 if (index >= 0) { 227 const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(index); 228 return info.layout->FirstOffsetOnLine(lineIndex) + paragraphOffset; 229 } 230 231 return 0; 232 } 233 234 235 int32 236 TextDocumentLayout::LastOffsetOnLine(int32 lineIndex) 237 { 238 int32 paragraphOffset; 239 int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset); 240 if (index >= 0) { 241 const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(index); 242 return info.layout->LastOffsetOnLine(lineIndex) + paragraphOffset; 243 } 244 245 return 0; 246 } 247 248 249 int32 250 TextDocumentLayout::CountLines() 251 { 252 _ValidateLayout(); 253 254 int32 lineCount = 0; 255 256 int32 count = fParagraphLayouts.CountItems(); 257 for (int32 i = 0; i < count; i++) { 258 const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(i); 259 lineCount += info.layout->CountLines(); 260 } 261 262 return lineCount; 263 } 264 265 266 void 267 TextDocumentLayout::GetLineBounds(int32 lineIndex, float& x1, float& y1, 268 float& x2, float& y2) 269 { 270 int32 paragraphOffset; 271 int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset); 272 if (index >= 0) { 273 const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(index); 274 info.layout->GetLineBounds(lineIndex, x1, y1, x2, y2); 275 y1 += info.y; 276 y2 += info.y; 277 return; 278 } 279 280 x1 = 0.0f; 281 y1 = 0.0f; 282 x2 = 0.0f; 283 y2 = 0.0f; 284 } 285 286 287 void 288 TextDocumentLayout::GetTextBounds(int32 textOffset, float& x1, float& y1, 289 float& x2, float& y2) 290 { 291 int32 index = _ParagraphLayoutIndexForOffset(textOffset); 292 if (index >= 0) { 293 const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(index); 294 info.layout->GetTextBounds(textOffset, x1, y1, x2, y2); 295 y1 += info.y; 296 y2 += info.y; 297 return; 298 } 299 300 x1 = 0.0f; 301 y1 = 0.0f; 302 x2 = 0.0f; 303 y2 = 0.0f; 304 } 305 306 307 int32 308 TextDocumentLayout::TextOffsetAt(float x, float y, bool& rightOfCenter) 309 { 310 _ValidateLayout(); 311 312 int32 textOffset = 0; 313 rightOfCenter = false; 314 315 int32 paragraphs = fParagraphLayouts.CountItems(); 316 for (int32 i = 0; i < paragraphs; i++) { 317 const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(i); 318 if (y > info.y + info.layout->Height()) { 319 textOffset += info.layout->CountGlyphs(); 320 continue; 321 } 322 323 textOffset += info.layout->TextOffsetAt(x, y - info.y, rightOfCenter); 324 break; 325 } 326 327 return textOffset; 328 } 329 330 // #pragma mark - private 331 332 333 void 334 TextDocumentLayout::_ValidateLayout() 335 { 336 if (!fLayoutValid) { 337 _Layout(); 338 fLayoutValid = true; 339 } 340 } 341 342 343 void 344 TextDocumentLayout::_Init() 345 { 346 fParagraphLayouts.Clear(); 347 348 if (fDocument.Get() == NULL) 349 return; 350 351 const ParagraphList& paragraphs = fDocument->Paragraphs(); 352 353 int paragraphCount = paragraphs.CountItems(); 354 for (int i = 0; i < paragraphCount; i++) { 355 const Paragraph& paragraph = paragraphs.ItemAtFast(i); 356 ParagraphLayoutRef layout(new(std::nothrow) ParagraphLayout(paragraph), 357 true); 358 if (layout.Get() == NULL 359 || !fParagraphLayouts.Add(ParagraphLayoutInfo(0.0f, layout))) { 360 fprintf(stderr, "TextDocumentLayout::_Layout() - out of memory\n"); 361 return; 362 } 363 } 364 } 365 366 367 void 368 TextDocumentLayout::_Layout() 369 { 370 float y = 0.0f; 371 372 int layoutCount = fParagraphLayouts.CountItems(); 373 for (int i = 0; i < layoutCount; i++) { 374 ParagraphLayoutInfo info = fParagraphLayouts.ItemAtFast(i); 375 const ParagraphStyle& style = info.layout->Style(); 376 377 if (i > 0) 378 y += style.SpacingTop(); 379 380 fParagraphLayouts.Replace(i, ParagraphLayoutInfo(y, info.layout)); 381 382 info.layout->SetWidth(fWidth); 383 y += info.layout->Height() + style.SpacingBottom(); 384 } 385 } 386 387 388 int32 389 TextDocumentLayout::_ParagraphLayoutIndexForOffset(int32& textOffset) 390 { 391 _ValidateLayout(); 392 393 int32 paragraphs = fParagraphLayouts.CountItems(); 394 for (int32 i = 0; i < paragraphs - 1; i++) { 395 const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(i); 396 397 int32 length = info.layout->CountGlyphs(); 398 if (textOffset >= length) { 399 textOffset -= length; 400 continue; 401 } 402 403 return i; 404 } 405 406 if (paragraphs > 0) { 407 const ParagraphLayoutInfo& info = fParagraphLayouts.LastItem(); 408 409 // Return last paragraph if the textOffset is still within or 410 // exactly behind the last valid offset in that paragraph. 411 int32 length = info.layout->CountGlyphs(); 412 if (textOffset <= length) 413 return paragraphs - 1; 414 } 415 416 return -1; 417 } 418 419 int32 420 TextDocumentLayout::_ParagraphLayoutIndexForLineIndex(int32& lineIndex, 421 int32& paragraphOffset) 422 { 423 _ValidateLayout(); 424 425 paragraphOffset = 0; 426 int32 paragraphs = fParagraphLayouts.CountItems(); 427 for (int32 i = 0; i < paragraphs; i++) { 428 const ParagraphLayoutInfo& info = fParagraphLayouts.ItemAtFast(i); 429 430 int32 lineCount = info.layout->CountLines(); 431 if (lineIndex >= lineCount) { 432 lineIndex -= lineCount; 433 paragraphOffset += info.layout->CountGlyphs(); 434 continue; 435 } 436 437 return i; 438 } 439 440 return -1; 441 } 442