#include "fibonacci_line_pattern.h" #include namespace radiantwave { FibonacciLinePattern::FibonacciLinePattern(int peakWordCount, float maxWidthPixels, float spaceWidth, WidthFunction widthFunction) : maxWidthPixels_(maxWidthPixels), spaceWidth_(spaceWidth), widthFunction_(std::move(widthFunction)) { // Build the Fibonacci numbers 1,1,2,3,5,8,... up to the peak, then cap the // oscillation with the peak itself so the widest line fills the screen. // E.g. peak 15 -> 1,1,2,3,5,8,13,15. sequence_.push_back(1); if (peakWordCount > 1) { sequence_.push_back(1); } int a = 1; int b = 1; while (true) { int next = a + b; if (next > peakWordCount) { break; } sequence_.push_back(next); a = b; b = next; } if (sequence_.back() < peakWordCount) { sequence_.push_back(peakWordCount); } } int FibonacciLinePattern::nextLine(const std::vector &words, std::vector &out) { if (words.empty()) { return 0; } // Wrap to the start of the word list at line boundaries only. Never wrap // mid-line, so a line is always a contiguous slice of the source text and // the scroll reads word-for-word like the proclamation itself. if (wordIndex_ >= static_cast(words.size())) { wordIndex_ = 0; } int target = sequence_[sequenceIndex_]; int placed = 0; float width = 0.0f; while (placed < target && wordIndex_ < static_cast(words.size())) { const std::string &word = words[wordIndex_]; float wordWidth = widthFunction_(word); float added = wordWidth + (width > 0.0f ? spaceWidth_ : 0.0f); // Always place the first word even if it alone exceeds the cap. if (placed > 0 && width + added > maxWidthPixels_) { break; } out.push_back(word); width += added; ++placed; ++wordIndex_; } // Walk the oscillation up and down: ..., 3,2,1,1,2,3,... const int size = static_cast(sequence_.size()); sequenceIndex_ += direction_; if (sequenceIndex_ >= size) { sequenceIndex_ = size >= 2 ? size - 2 : 0; direction_ = -1; } else if (sequenceIndex_ < 0) { sequenceIndex_ = size >= 3 ? 2 : 0; direction_ = 1; } return placed; } } // namespace radiantwave