/* * Copyright 2013-2015, Stephan Aßmus . * All rights reserved. Distributed under the terms of the MIT License. */ #include "TextDocumentLayout.h" #include #include #include class LayoutTextListener : public TextListener { public: LayoutTextListener(TextDocumentLayout* layout) : fLayout(layout) { } virtual void TextChanging(TextChangingEvent& event) { } virtual void TextChanged(const TextChangedEvent& event) { // printf("TextChanged(%" B_PRIi32 ", %" B_PRIi32 ")\n", // event.FirstChangedParagraph(), // event.ChangedParagraphCount()); // TODO: The event does not contain useful data. Make the event // system work so only the affected paragraphs are updated. // I think we need "first affected", "last affected" (both relative // to the original paragraph count), and than how many new paragraphs // are between these. From the difference in old number of paragraphs // inbetween and the new count, we know how many new paragraphs are // missing, and the rest in the range needs to be updated. // fLayout->InvalidateParagraphs(event.FirstChangedParagraph(), // event.ChangedParagraphCount()); fLayout->Invalidate(); } private: TextDocumentLayout* fLayout; }; TextDocumentLayout::TextDocumentLayout() : fWidth(0.0f), fLayoutValid(false), fDocument(), fTextListener(new(std::nothrow) LayoutTextListener(this), true), fParagraphLayouts() { } TextDocumentLayout::TextDocumentLayout(const TextDocumentRef& document) : fWidth(0.0f), fLayoutValid(false), fDocument(), fTextListener(new(std::nothrow) LayoutTextListener(this), true), fParagraphLayouts() { SetTextDocument(document); } TextDocumentLayout::TextDocumentLayout(const TextDocumentLayout& other) : fWidth(other.fWidth), fLayoutValid(other.fLayoutValid), fDocument(other.fDocument), fTextListener(new(std::nothrow) LayoutTextListener(this), true), fParagraphLayouts(other.fParagraphLayouts) { if (fDocument.IsSet()) fDocument->AddListener(fTextListener); } TextDocumentLayout::~TextDocumentLayout() { SetTextDocument(NULL); } void TextDocumentLayout::SetTextDocument(const TextDocumentRef& document) { if (fDocument == document) return; if (fDocument.IsSet()) fDocument->RemoveListener(fTextListener); fDocument = document; _Init(); fLayoutValid = false; if (fDocument.IsSet()) fDocument->AddListener(fTextListener); } void TextDocumentLayout::Invalidate() { if (fDocument.IsSet()) InvalidateParagraphs(0, fDocument->CountParagraphs()); } void TextDocumentLayout::InvalidateParagraphs(int32 start, int32 count) { if (start < 0 || count == 0 || !fDocument.IsSet()) return; fLayoutValid = false; while (count > 0) { const int32 paragraphCount = fDocument->CountParagraphs(); if (start >= paragraphCount) break; const Paragraph& paragraph = fDocument->ParagraphAtIndex(start); if (start >= static_cast(fParagraphLayouts.size())) { ParagraphLayoutRef layout(new(std::nothrow) ParagraphLayout( paragraph), true); if (!layout.IsSet()) { fprintf(stderr, "TextDocumentLayout::InvalidateParagraphs() - " "out of memory\n"); return; } try { fParagraphLayouts.push_back(ParagraphLayoutInfo(0.0f, layout)); } catch (std::bad_alloc& ba) { fprintf(stderr, "bad_alloc when invalidating paragraphs\n"); return; } } else { const ParagraphLayoutInfo& info = fParagraphLayouts[start]; info.layout->SetParagraph(paragraph); } start++; count--; } // Remove any extra paragraph layouts while (fDocument->CountParagraphs() < static_cast(fParagraphLayouts.size())) fParagraphLayouts.erase(fParagraphLayouts.end() - 1); } void TextDocumentLayout::SetWidth(float width) { if (fWidth != width) { fWidth = width; fLayoutValid = false; } } float TextDocumentLayout::Height() { _ValidateLayout(); float height = 0.0f; if (fParagraphLayouts.size() > 0) { const ParagraphLayoutInfo& lastLayout = fParagraphLayouts[fParagraphLayouts.size() - 1]; height = lastLayout.y + lastLayout.layout->Height(); } return height; } void TextDocumentLayout::Draw(BView* view, const BPoint& offset, const BRect& updateRect) { _ValidateLayout(); int layoutCount = fParagraphLayouts.size(); for (int i = 0; i < layoutCount; i++) { const ParagraphLayoutInfo& layout = fParagraphLayouts[i]; BPoint location(offset.x, offset.y + layout.y); if (location.y > updateRect.bottom) break; if (location.y + layout.layout->Height() > updateRect.top) layout.layout->Draw(view, location); } } int32 TextDocumentLayout::LineIndexForOffset(int32 textOffset) { int32 index = _ParagraphLayoutIndexForOffset(textOffset); if (index >= 0) { int32 lineIndex = 0; for (int32 i = 0; i < index; i++) { lineIndex += fParagraphLayouts[i].layout->CountLines(); } const ParagraphLayoutInfo& info = fParagraphLayouts[index]; return lineIndex + info.layout->LineIndexForOffset(textOffset); } return 0; } int32 TextDocumentLayout::FirstOffsetOnLine(int32 lineIndex) { int32 paragraphOffset; int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset); if (index >= 0) { const ParagraphLayoutInfo& info = fParagraphLayouts[index]; return info.layout->FirstOffsetOnLine(lineIndex) + paragraphOffset; } return 0; } int32 TextDocumentLayout::LastOffsetOnLine(int32 lineIndex) { int32 paragraphOffset; int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset); if (index >= 0) { const ParagraphLayoutInfo& info = fParagraphLayouts[index]; return info.layout->LastOffsetOnLine(lineIndex) + paragraphOffset; } return 0; } int32 TextDocumentLayout::CountLines() { _ValidateLayout(); int32 lineCount = 0; int32 count = fParagraphLayouts.size(); for (int32 i = 0; i < count; i++) { const ParagraphLayoutInfo& info = fParagraphLayouts[i]; lineCount += info.layout->CountLines(); } return lineCount; } void TextDocumentLayout::GetLineBounds(int32 lineIndex, float& x1, float& y1, float& x2, float& y2) { int32 paragraphOffset; int32 index = _ParagraphLayoutIndexForLineIndex(lineIndex, paragraphOffset); if (index >= 0) { const ParagraphLayoutInfo& info = fParagraphLayouts[index]; info.layout->GetLineBounds(lineIndex, x1, y1, x2, y2); y1 += info.y; y2 += info.y; return; } x1 = 0.0f; y1 = 0.0f; x2 = 0.0f; y2 = 0.0f; } void TextDocumentLayout::GetTextBounds(int32 textOffset, float& x1, float& y1, float& x2, float& y2) { int32 index = _ParagraphLayoutIndexForOffset(textOffset); if (index >= 0) { const ParagraphLayoutInfo& info = fParagraphLayouts[index]; info.layout->GetTextBounds(textOffset, x1, y1, x2, y2); y1 += info.y; y2 += info.y; return; } x1 = 0.0f; y1 = 0.0f; x2 = 0.0f; y2 = 0.0f; } int32 TextDocumentLayout::TextOffsetAt(float x, float y, bool& rightOfCenter) { _ValidateLayout(); int32 textOffset = 0; rightOfCenter = false; int32 paragraphs = fParagraphLayouts.size(); for (int32 i = 0; i < paragraphs; i++) { const ParagraphLayoutInfo& info = fParagraphLayouts[i]; if (y > info.y + info.layout->Height()) { textOffset += info.layout->CountGlyphs(); continue; } textOffset += info.layout->TextOffsetAt(x, y - info.y, rightOfCenter); break; } return textOffset; } // #pragma mark - private void TextDocumentLayout::_ValidateLayout() { if (!fLayoutValid) { _Layout(); fLayoutValid = true; } } void TextDocumentLayout::_Init() { fParagraphLayouts.clear(); if (!fDocument.IsSet()) return; int paragraphCount = fDocument->CountParagraphs(); for (int i = 0; i < paragraphCount; i++) { const Paragraph& paragraph = fDocument->ParagraphAtIndex(i); ParagraphLayoutRef layout(new(std::nothrow) ParagraphLayout(paragraph), true); if (!layout.IsSet()) { fprintf(stderr, "TextDocumentLayout::_Layout() - out of memory\n"); return; } try { fParagraphLayouts.push_back(ParagraphLayoutInfo(0.0f, layout)); } catch (std::bad_alloc& ba) { fprintf(stderr, "bad_alloc when inititalizing the text document " "layout\n"); return; } } } void TextDocumentLayout::_Layout() { float y = 0.0f; int layoutCount = fParagraphLayouts.size(); for (int i = 0; i < layoutCount; i++) { ParagraphLayoutInfo info = fParagraphLayouts[i]; const ParagraphStyle& style = info.layout->Style(); if (i > 0) y += style.SpacingTop(); fParagraphLayouts[i] = ParagraphLayoutInfo(y, info.layout); info.layout->SetWidth(fWidth); y += info.layout->Height() + style.SpacingBottom(); } } int32 TextDocumentLayout::_ParagraphLayoutIndexForOffset(int32& textOffset) { _ValidateLayout(); int32 paragraphs = fParagraphLayouts.size(); for (int32 i = 0; i < paragraphs - 1; i++) { const ParagraphLayoutInfo& info = fParagraphLayouts[i]; int32 length = info.layout->CountGlyphs(); if (textOffset >= length) { textOffset -= length; continue; } return i; } if (paragraphs > 0) { const ParagraphLayoutInfo& info = fParagraphLayouts[fParagraphLayouts.size() - 1]; // Return last paragraph if the textOffset is still within or // exactly behind the last valid offset in that paragraph. int32 length = info.layout->CountGlyphs(); if (textOffset <= length) return paragraphs - 1; } return -1; } int32 TextDocumentLayout::_ParagraphLayoutIndexForLineIndex(int32& lineIndex, int32& paragraphOffset) { _ValidateLayout(); paragraphOffset = 0; int32 paragraphs = fParagraphLayouts.size(); for (int32 i = 0; i < paragraphs; i++) { const ParagraphLayoutInfo& info = fParagraphLayouts[i]; int32 lineCount = info.layout->CountLines(); if (lineIndex >= lineCount) { lineIndex -= lineCount; paragraphOffset += info.layout->CountGlyphs(); continue; } return i; } return -1; }