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