* Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2021, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "Paragraph.h"
#include <algorithm>
#include <stdio.h>
Paragraph::Paragraph()
:
fStyle()
{
}
Paragraph::Paragraph(const ParagraphStyle& style)
:
fStyle(style),
fTextSpans(),
fCachedLength(-1)
{
}
Paragraph::Paragraph(const Paragraph& other)
:
fStyle(other.fStyle),
fTextSpans(other.fTextSpans),
fCachedLength(other.fCachedLength)
{
}
Paragraph&
Paragraph::operator=(const Paragraph& other)
{
fStyle = other.fStyle;
fTextSpans = other.fTextSpans;
fCachedLength = other.fCachedLength;
return *this;
}
bool
Paragraph::operator==(const Paragraph& other) const
{
if (this == &other)
return true;
return fStyle == other.fStyle
&& fTextSpans == other.fTextSpans;
}
bool
Paragraph::operator!=(const Paragraph& other) const
{
return !(*this == other);
}
int32
Paragraph::CountTextSpans() const
{
return static_cast<int32>(fTextSpans.size());
}
const TextSpan&
Paragraph::TextSpanAtIndex(int32 index) const
{
return fTextSpans[index];
}
void
Paragraph::SetStyle(const ParagraphStyle& style)
{
fStyle = style;
}
bool
Paragraph::Prepend(const TextSpan& span)
{
_InvalidateCachedLength();
if (!fTextSpans.empty()) {
const TextSpan& firstSpan = fTextSpans[0];
if (firstSpan.Style() == span.Style()) {
BString text(span.Text());
text.Append(firstSpan.Text());
fTextSpans[0] = TextSpan(text, span.Style());
return true;
}
}
fTextSpans.push_back(span);
return true;
}
bool
Paragraph::Append(const TextSpan& span)
{
_InvalidateCachedLength();
if (!fTextSpans.empty()) {
const TextSpan& lastSpan = fTextSpans[fTextSpans.size() - 1];
if (lastSpan.Style() == span.Style()) {
BString text(lastSpan.Text());
text.Append(span.Text());
fTextSpans.pop_back();
fTextSpans.push_back(TextSpan(text, span.Style()));
return true;
}
}
fTextSpans.push_back(span);
return true;
}
bool
Paragraph::Insert(int32 offset, const TextSpan& newSpan)
{
_InvalidateCachedLength();
int32 index = 0;
{
int32 countTextSpans = static_cast<int32>(fTextSpans.size());
while (index < countTextSpans) {
const TextSpan& span = fTextSpans[index];
if (offset - span.CountChars() < 0)
break;
offset -= span.CountChars();
index++;
}
if (countTextSpans == index)
return Append(newSpan);
}
TextSpan span = fTextSpans[index];
if (span.Style() == newSpan.Style()) {
span.Insert(offset, newSpan.Text());
fTextSpans[index] = span;
return true;
}
if (offset == 0) {
if (index > 0) {
TextSpan span = fTextSpans[index - 1];
if (span.Style() == newSpan.Style()) {
span.Insert(span.CountChars(), newSpan.Text());
fTextSpans[index - 1] = span;
return true;
}
}
fTextSpans.insert(fTextSpans.begin() + index, newSpan);
return true;
}
TextSpan spanBefore = span.SubSpan(0, offset);
TextSpan spanAfter = span.SubSpan(offset, span.CountChars() - offset);
fTextSpans[index] = spanBefore;
fTextSpans.insert(fTextSpans.begin() + (index + 1), newSpan);
fTextSpans.insert(fTextSpans.begin() + (index + 2), spanAfter);
return true;
}
bool
Paragraph::Remove(int32 offset, int32 length)
{
if (length == 0)
return true;
_InvalidateCachedLength();
int32 index = 0;
{
int32 countTextSpans = static_cast<int32>(fTextSpans.size());
while (index < countTextSpans) {
const TextSpan& span = fTextSpans[index];
if (offset - span.CountChars() < 0)
break;
offset -= span.CountChars();
index++;
}
if (index >= countTextSpans)
return false;
}
TextSpan span(fTextSpans[index]);
int32 removeLength = std::min(span.CountChars() - offset, length);
span.Remove(offset, removeLength);
length -= removeLength;
index += 1;
while (length > 0 && index < static_cast<int32>(fTextSpans.size())) {
int32 spanLength = fTextSpans[index].CountChars();
if (spanLength <= length) {
fTextSpans.erase(fTextSpans.begin() + index);
length -= spanLength;
} else {
removeLength = std::min(length, spanLength);
TextSpan lastSpan = fTextSpans[index].SubSpan(
removeLength, spanLength - removeLength);
if (lastSpan.Style() == span.Style()) {
span.Insert(span.CountChars(), lastSpan.Text());
fTextSpans.erase(fTextSpans.begin() + index);
} else {
fTextSpans[index] = lastSpan;
}
break;
}
}
index--;
if (span.CountChars() > 0 || static_cast<int32>(fTextSpans.size()) == 1) {
fTextSpans[index] = span;
} else {
fTextSpans.erase(fTextSpans.begin() + index);
index--;
}
if (index >= 0 && index + 1 < static_cast<int32>(fTextSpans.size())) {
const TextSpan& span1 = fTextSpans[index];
const TextSpan& span2 = fTextSpans[index + 1];
if (span1.Style() == span2.Style()) {
span = span1;
span.Append(span2.Text());
fTextSpans[index] = span;
fTextSpans.erase(fTextSpans.begin() + (index + 1));
}
}
return true;
}
void
Paragraph::Clear()
{
fTextSpans.clear();
}
int32
Paragraph::Length() const
{
if (fCachedLength >= 0)
return fCachedLength;
int32 length = 0;
std::vector<TextSpan>::const_iterator it;
for (it = fTextSpans.begin(); it != fTextSpans.end(); it++) {
const TextSpan& span = *it;
length += span.CountChars();
}
fCachedLength = length;
return length;
}
bool
Paragraph::IsEmpty() const
{
return fTextSpans.empty();
}
bool
Paragraph::EndsWith(BString string) const
{
int length = Length();
int endLength = string.CountChars();
int start = length - endLength;
BString end = Text(start, endLength);
return end == string;
}
BString
Paragraph::Text() const
{
BString result;
std::vector<TextSpan>::const_iterator it;
for (it = fTextSpans.begin(); it != fTextSpans.end(); it++) {
const TextSpan& span = *it;
result << span.Text();
}
return result;
}
BString
Paragraph::Text(int32 start, int32 length) const
{
Paragraph subParagraph = SubParagraph(start, length);
return subParagraph.Text();
}
Paragraph
Paragraph::SubParagraph(int32 start, int32 length) const
{
if (start < 0)
start = 0;
if (start == 0 && length == Length())
return *this;
Paragraph result(fStyle);
std::vector<TextSpan>::const_iterator it;
for (it = fTextSpans.begin(); it != fTextSpans.end(); it++) {
const TextSpan& span = *it;
int32 spanLength = span.CountChars();
if (spanLength == 0)
continue;
if (start > spanLength) {
start -= spanLength;
continue;
}
spanLength -= start;
int32 copyLength = std::min(spanLength, length);
if (start == 0 && length == spanLength)
result.Append(span);
else
result.Append(span.SubSpan(start, copyLength));
length -= copyLength;
if (length == 0)
break;
start = 0;
}
return result;
}
void
Paragraph::PrintToStream() const
{
int32 spanCount = static_cast<int32>(fTextSpans.size());
if (spanCount == 0) {
printf(" <p/>\n");
return;
}
printf(" <p>\n");
for (int32 i = 0; i < spanCount; i++) {
const TextSpan& span = fTextSpans[i];
if (span.CountChars() == 0)
printf(" <span/>\n");
else {
BString text = span.Text();
text.ReplaceAll("\n", "\\n");
printf(" <span>%s</span>\n", text.String());
}
}
printf(" </p>\n");
}
void
Paragraph::_InvalidateCachedLength()
{
fCachedLength = -1;
}