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 14class LayoutTextListener : public TextListener { 15public: 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 43private: 44 TextDocumentLayout* fLayout; 45}; 46 47 48 49TextDocumentLayout::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 61TextDocumentLayout::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 74TextDocumentLayout::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.IsSet()) 84 fDocument->AddListener(fTextListener); 85} 86 87 88TextDocumentLayout::~TextDocumentLayout() 89{ 90 SetTextDocument(NULL); 91} 92 93 94void 95TextDocumentLayout::SetTextDocument(const TextDocumentRef& document) 96{ 97 if (fDocument == document) 98 return; 99 100 if (fDocument.IsSet()) 101 fDocument->RemoveListener(fTextListener); 102 103 fDocument = document; 104 _Init(); 105 fLayoutValid = false; 106 107 if (fDocument.IsSet()) 108 fDocument->AddListener(fTextListener); 109} 110 111 112void 113TextDocumentLayout::Invalidate() 114{ 115 if (fDocument.IsSet()) 116 InvalidateParagraphs(0, fDocument->CountParagraphs()); 117} 118 119 120void 121TextDocumentLayout::InvalidateParagraphs(int32 start, int32 count) 122{ 123 if (start < 0 || count == 0 || !fDocument.IsSet()) 124 return; 125 126 fLayoutValid = false; 127 128 while (count > 0) { 129 const int32 paragraphCount = fDocument->CountParagraphs(); 130 if (start >= paragraphCount) 131 break; 132 const Paragraph& paragraph = fDocument->ParagraphAtIndex(start); 133 if (start >= static_cast<int32>(fParagraphLayouts.size())) { 134 ParagraphLayoutRef layout(new(std::nothrow) ParagraphLayout( 135 paragraph), true); 136 if (!layout.IsSet()) { 137 fprintf(stderr, "TextDocumentLayout::InvalidateParagraphs() - " 138 "out of memory\n"); 139 return; 140 } 141 try { 142 fParagraphLayouts.push_back(ParagraphLayoutInfo(0.0f, layout)); 143 } 144 catch (std::bad_alloc& ba) { 145 fprintf(stderr, "bad_alloc when invalidating paragraphs\n"); 146 return; 147 } 148 } else { 149 const ParagraphLayoutInfo& info = fParagraphLayouts[start]; 150 info.layout->SetParagraph(paragraph); 151 } 152 153 start++; 154 count--; 155 } 156 157 // Remove any extra paragraph layouts 158 while (fDocument->CountParagraphs() 159 < static_cast<int32>(fParagraphLayouts.size())) 160 fParagraphLayouts.erase(fParagraphLayouts.end() - 1); 161} 162 163 164void 165TextDocumentLayout::SetWidth(float width) 166{ 167 if (fWidth != width) { 168 fWidth = width; 169 fLayoutValid = false; 170 } 171} 172 173 174float 175TextDocumentLayout::Height() 176{ 177 _ValidateLayout(); 178 179 float height = 0.0f; 180 181 if (fParagraphLayouts.size() > 0) { 182 const ParagraphLayoutInfo& lastLayout 183 = fParagraphLayouts[fParagraphLayouts.size() - 1]; 184 height = lastLayout.y + lastLayout.layout->Height(); 185 } 186 187 return height; 188} 189 190 191void 192TextDocumentLayout::Draw(BView* view, const BPoint& offset, 193 const BRect& updateRect) 194{ 195 _ValidateLayout(); 196 197 int layoutCount = fParagraphLayouts.size(); 198 for (int i = 0; i < layoutCount; i++) { 199 const ParagraphLayoutInfo& layout = fParagraphLayouts[i]; 200 BPoint location(offset.x, offset.y + layout.y); 201 if (location.y > updateRect.bottom) 202 break; 203 if (location.y + layout.layout->Height() > updateRect.top) 204 layout.layout->Draw(view, location); 205 } 206} 207 208 209int32 210TextDocumentLayout::LineIndexForOffset(int32 textOffset) 211{ 212 int32 index = _ParagraphLayoutIndexForOffset(textOffset); 213 if (index >= 0) { 214 int32 lineIndex = 0; 215 for (int32 i = 0; i < index; i++) { 216 lineIndex += fParagraphLayouts[i].layout->CountLines(); 217 } 218 219 const ParagraphLayoutInfo& info = fParagraphLayouts[index]; 220 return lineIndex + info.layout->LineIndexForOffset(textOffset); 221 } 222 223 return 0; 224} 225 226 227int32 228TextDocumentLayout::FirstOffsetOnLine(int32 lineIndex) 229{ 230 int32 paragraphOffset; 231 int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset); 232 if (index >= 0) { 233 const ParagraphLayoutInfo& info = fParagraphLayouts[index]; 234 return info.layout->FirstOffsetOnLine(lineIndex) + paragraphOffset; 235 } 236 237 return 0; 238} 239 240 241int32 242TextDocumentLayout::LastOffsetOnLine(int32 lineIndex) 243{ 244 int32 paragraphOffset; 245 int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset); 246 if (index >= 0) { 247 const ParagraphLayoutInfo& info = fParagraphLayouts[index]; 248 return info.layout->LastOffsetOnLine(lineIndex) + paragraphOffset; 249 } 250 251 return 0; 252} 253 254 255int32 256TextDocumentLayout::CountLines() 257{ 258 _ValidateLayout(); 259 260 int32 lineCount = 0; 261 262 int32 count = fParagraphLayouts.size(); 263 for (int32 i = 0; i < count; i++) { 264 const ParagraphLayoutInfo& info = fParagraphLayouts[i]; 265 lineCount += info.layout->CountLines(); 266 } 267 268 return lineCount; 269} 270 271 272void 273TextDocumentLayout::GetLineBounds(int32 lineIndex, float& x1, float& y1, 274 float& x2, float& y2) 275{ 276 int32 paragraphOffset; 277 int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset); 278 if (index >= 0) { 279 const ParagraphLayoutInfo& info = fParagraphLayouts[index]; 280 info.layout->GetLineBounds(lineIndex, x1, y1, x2, y2); 281 y1 += info.y; 282 y2 += info.y; 283 return; 284 } 285 286 x1 = 0.0f; 287 y1 = 0.0f; 288 x2 = 0.0f; 289 y2 = 0.0f; 290} 291 292 293void 294TextDocumentLayout::GetTextBounds(int32 textOffset, float& x1, float& y1, 295 float& x2, float& y2) 296{ 297 int32 index = _ParagraphLayoutIndexForOffset(textOffset); 298 if (index >= 0) { 299 const ParagraphLayoutInfo& info = fParagraphLayouts[index]; 300 info.layout->GetTextBounds(textOffset, x1, y1, x2, y2); 301 y1 += info.y; 302 y2 += info.y; 303 return; 304 } 305 306 x1 = 0.0f; 307 y1 = 0.0f; 308 x2 = 0.0f; 309 y2 = 0.0f; 310} 311 312 313int32 314TextDocumentLayout::TextOffsetAt(float x, float y, bool& rightOfCenter) 315{ 316 _ValidateLayout(); 317 318 int32 textOffset = 0; 319 rightOfCenter = false; 320 321 int32 paragraphs = fParagraphLayouts.size(); 322 for (int32 i = 0; i < paragraphs; i++) { 323 const ParagraphLayoutInfo& info = fParagraphLayouts[i]; 324 if (y > info.y + info.layout->Height()) { 325 textOffset += info.layout->CountGlyphs(); 326 continue; 327 } 328 329 textOffset += info.layout->TextOffsetAt(x, y - info.y, rightOfCenter); 330 break; 331 } 332 333 return textOffset; 334} 335 336// #pragma mark - private 337 338 339void 340TextDocumentLayout::_ValidateLayout() 341{ 342 if (!fLayoutValid) { 343 _Layout(); 344 fLayoutValid = true; 345 } 346} 347 348 349void 350TextDocumentLayout::_Init() 351{ 352 fParagraphLayouts.clear(); 353 354 if (!fDocument.IsSet()) 355 return; 356 357 int paragraphCount = fDocument->CountParagraphs(); 358 for (int i = 0; i < paragraphCount; i++) { 359 const Paragraph& paragraph = fDocument->ParagraphAtIndex(i); 360 ParagraphLayoutRef layout(new(std::nothrow) ParagraphLayout(paragraph), 361 true); 362 if (!layout.IsSet()) { 363 fprintf(stderr, "TextDocumentLayout::_Layout() - out of memory\n"); 364 return; 365 } 366 try { 367 fParagraphLayouts.push_back(ParagraphLayoutInfo(0.0f, layout)); 368 } 369 catch (std::bad_alloc& ba) { 370 fprintf(stderr, "bad_alloc when inititalizing the text document " 371 "layout\n"); 372 return; 373 } 374 } 375} 376 377 378void 379TextDocumentLayout::_Layout() 380{ 381 float y = 0.0f; 382 383 int layoutCount = fParagraphLayouts.size(); 384 for (int i = 0; i < layoutCount; i++) { 385 ParagraphLayoutInfo info = fParagraphLayouts[i]; 386 const ParagraphStyle& style = info.layout->Style(); 387 388 if (i > 0) 389 y += style.SpacingTop(); 390 391 fParagraphLayouts[i] = ParagraphLayoutInfo(y, info.layout); 392 393 info.layout->SetWidth(fWidth); 394 y += info.layout->Height() + style.SpacingBottom(); 395 } 396} 397 398 399int32 400TextDocumentLayout::_ParagraphLayoutIndexForOffset(int32& textOffset) 401{ 402 _ValidateLayout(); 403 404 int32 paragraphs = fParagraphLayouts.size(); 405 for (int32 i = 0; i < paragraphs - 1; i++) { 406 const ParagraphLayoutInfo& info = fParagraphLayouts[i]; 407 408 int32 length = info.layout->CountGlyphs(); 409 if (textOffset >= length) { 410 textOffset -= length; 411 continue; 412 } 413 414 return i; 415 } 416 417 if (paragraphs > 0) { 418 const ParagraphLayoutInfo& info 419 = fParagraphLayouts[fParagraphLayouts.size() - 1]; 420 421 // Return last paragraph if the textOffset is still within or 422 // exactly behind the last valid offset in that paragraph. 423 int32 length = info.layout->CountGlyphs(); 424 if (textOffset <= length) 425 return paragraphs - 1; 426 } 427 428 return -1; 429} 430 431int32 432TextDocumentLayout::_ParagraphLayoutIndexForLineIndex(int32& lineIndex, 433 int32& paragraphOffset) 434{ 435 _ValidateLayout(); 436 437 paragraphOffset = 0; 438 int32 paragraphs = fParagraphLayouts.size(); 439 for (int32 i = 0; i < paragraphs; i++) { 440 const ParagraphLayoutInfo& info = fParagraphLayouts[i]; 441 442 int32 lineCount = info.layout->CountLines(); 443 if (lineIndex >= lineCount) { 444 lineIndex -= lineCount; 445 paragraphOffset += info.layout->CountGlyphs(); 446 continue; 447 } 448 449 return i; 450 } 451 452 return -1; 453} 454