// Copyright 2024 The PDFium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com #include "core/fxcrt/string_template.h" #include #include #include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/span.h" #include "core/fxcrt/span_util.h" namespace fxcrt { template pdfium::span StringTemplate::GetBuffer(size_t nMinBufLength) { if (!m_pData) { if (nMinBufLength == 0) { return pdfium::span(); } m_pData = StringData::Create(nMinBufLength); m_pData->m_nDataLength = 0; m_pData->m_String[0] = 0; return m_pData->alloc_span(); } if (m_pData->CanOperateInPlace(nMinBufLength)) { return m_pData->alloc_span(); } nMinBufLength = std::max(nMinBufLength, m_pData->m_nDataLength); if (nMinBufLength == 0) { return pdfium::span(); } RetainPtr pNewData = StringData::Create(nMinBufLength); pNewData->CopyContents(*m_pData); pNewData->m_nDataLength = m_pData->m_nDataLength; m_pData = std::move(pNewData); return m_pData->alloc_span(); } template void StringTemplate::ReleaseBuffer(size_t nNewLength) { if (!m_pData) { return; } nNewLength = std::min(nNewLength, m_pData->m_nAllocLength); if (nNewLength == 0) { clear(); return; } DCHECK_EQ(m_pData->m_nRefs, 1); m_pData->m_nDataLength = nNewLength; m_pData->capacity_span()[nNewLength] = 0; if (m_pData->m_nAllocLength - nNewLength >= 32) { // Over arbitrary threshold, so pay the price to relocate. Force copy to // always occur by holding a second reference to the string. StringTemplate preserve(*this); ReallocBeforeWrite(nNewLength); } } template size_t StringTemplate::Remove(T chRemove) { size_t count = std::count(span().begin(), span().end(), chRemove); if (count == 0) { return 0; } ReallocBeforeWrite(m_pData->m_nDataLength); auto src_span = m_pData->span(); auto dst_span = m_pData->span(); // Perform self-intersecting copy in forwards order. while (!src_span.empty()) { if (src_span[0] != chRemove) { dst_span[0] = src_span[0]; dst_span = dst_span.subspan(1); } src_span = src_span.subspan(1); } m_pData->m_nDataLength -= count; m_pData->capacity_span()[m_pData->m_nDataLength] = 0; return count; } template size_t StringTemplate::Insert(size_t index, T ch) { const size_t cur_length = GetLength(); if (!IsValidLength(index)) { return cur_length; } const size_t new_length = cur_length + 1; ReallocBeforeWrite(new_length); fxcrt::spanmove(m_pData->capacity_span().subspan(index + 1), m_pData->capacity_span().subspan(index, new_length - index)); m_pData->capacity_span()[index] = ch; m_pData->m_nDataLength = new_length; return new_length; } template size_t StringTemplate::Delete(size_t index, size_t count) { if (!m_pData) { return 0; } size_t old_length = m_pData->m_nDataLength; if (count == 0 || index != std::clamp(index, 0, old_length)) { return old_length; } size_t removal_length = index + count; if (removal_length > old_length) { return old_length; } ReallocBeforeWrite(old_length); // Include the NUL char not accounted for in lengths. size_t chars_to_copy = old_length - removal_length + 1; fxcrt::spanmove( m_pData->capacity_span().subspan(index), m_pData->capacity_span().subspan(removal_length, chars_to_copy)); m_pData->m_nDataLength = old_length - count; return m_pData->m_nDataLength; } template void StringTemplate::SetAt(size_t index, T ch) { DCHECK(IsValidIndex(index)); ReallocBeforeWrite(m_pData->m_nDataLength); m_pData->span()[index] = ch; } template std::optional StringTemplate::Find(T ch, size_t start) const { return Find(StringView(ch), start); } template std::optional StringTemplate::Find(StringView str, size_t start) const { if (!m_pData) { return std::nullopt; } if (!IsValidIndex(start)) { return std::nullopt; } std::optional result = spanpos(m_pData->span().subspan(start), str.span()); if (!result.has_value()) { return std::nullopt; } return start + result.value(); } template std::optional StringTemplate::ReverseFind(T ch) const { if (!m_pData) { return std::nullopt; } size_t nLength = m_pData->m_nDataLength; while (nLength--) { if (m_pData->span()[nLength] == ch) { return nLength; } } return std::nullopt; } template size_t StringTemplate::Replace(StringView oldstr, StringView newstr) { if (!m_pData || oldstr.IsEmpty()) { return 0; } size_t count = 0; { // Limit span lifetime. pdfium::span search_span = m_pData->span(); while (true) { std::optional found = spanpos(search_span, oldstr.span()); if (!found.has_value()) { break; } ++count; search_span = search_span.subspan(found.value() + oldstr.GetLength()); } } if (count == 0) { return 0; } size_t nNewLength = m_pData->m_nDataLength + count * (newstr.GetLength() - oldstr.GetLength()); if (nNewLength == 0) { clear(); return count; } RetainPtr newstr_data = StringData::Create(nNewLength); { // Spans can't outlive StringData buffers. pdfium::span search_span = m_pData->span(); pdfium::span dest_span = newstr_data->span(); for (size_t i = 0; i < count; i++) { size_t found = spanpos(search_span, oldstr.span()).value(); dest_span = spancpy(dest_span, search_span.first(found)); dest_span = spancpy(dest_span, newstr.span()); search_span = search_span.subspan(found + oldstr.GetLength()); } dest_span = spancpy(dest_span, search_span); CHECK(dest_span.empty()); } m_pData = std::move(newstr_data); return count; } template void StringTemplate::Trim(T ch) { TrimFront(ch); TrimBack(ch); } template void StringTemplate::TrimFront(T ch) { TrimFront(StringView(ch)); } template void StringTemplate::TrimBack(T ch) { TrimBack(StringView(ch)); } template void StringTemplate::Trim(StringView targets) { TrimFront(targets); TrimBack(targets); } template void StringTemplate::TrimFront(StringView targets) { if (!m_pData || targets.IsEmpty()) { return; } size_t len = GetLength(); if (len == 0) { return; } size_t pos = 0; while (pos < len) { size_t i = 0; while (i < targets.GetLength() && targets.CharAt(i) != m_pData->span()[pos]) { i++; } if (i == targets.GetLength()) { break; } pos++; } if (!pos) { return; } ReallocBeforeWrite(len); size_t nDataLength = len - pos; // Move the terminating NUL as well. fxcrt::spanmove(m_pData->capacity_span(), m_pData->capacity_span().subspan(pos, nDataLength + 1)); m_pData->m_nDataLength = nDataLength; } template void StringTemplate::TrimBack(StringView targets) { if (!m_pData || targets.IsEmpty()) { return; } size_t pos = GetLength(); if (pos == 0) { return; } while (pos) { size_t i = 0; while (i < targets.GetLength() && targets.CharAt(i) != m_pData->span()[pos - 1]) { i++; } if (i == targets.GetLength()) { break; } pos--; } if (pos < m_pData->m_nDataLength) { ReallocBeforeWrite(m_pData->m_nDataLength); m_pData->m_nDataLength = pos; m_pData->capacity_span()[m_pData->m_nDataLength] = 0; } } template void StringTemplate::ReallocBeforeWrite(size_t nNewLength) { if (m_pData && m_pData->CanOperateInPlace(nNewLength)) { return; } if (nNewLength == 0) { clear(); return; } RetainPtr pNewData = StringData::Create(nNewLength); if (m_pData) { size_t nCopyLength = std::min(m_pData->m_nDataLength, nNewLength); // SAFETY: copy of no more than m_nDataLength bytes. pNewData->CopyContents( UNSAFE_BUFFERS(pdfium::make_span(m_pData->m_String, nCopyLength))); pNewData->m_nDataLength = nCopyLength; } else { pNewData->m_nDataLength = 0; } pNewData->capacity_span()[pNewData->m_nDataLength] = 0; m_pData = std::move(pNewData); } template void StringTemplate::AllocBeforeWrite(size_t nNewLength) { if (m_pData && m_pData->CanOperateInPlace(nNewLength)) { return; } if (nNewLength == 0) { clear(); return; } m_pData = StringData::Create(nNewLength); } template void StringTemplate::AssignCopy(const T* pSrcData, size_t nSrcLen) { AllocBeforeWrite(nSrcLen); // SAFETY: AllocBeforeWrite() ensures `nSrcLen` bytes available. m_pData->CopyContents(UNSAFE_BUFFERS(pdfium::make_span(pSrcData, nSrcLen))); m_pData->m_nDataLength = nSrcLen; } template void StringTemplate::Concat(const T* pSrcData, size_t nSrcLen) { if (!pSrcData || nSrcLen == 0) { return; } // SAFETY: required from caller. // TODO(tsepez): should be UNSAFE_BUFFER_USAGE or pass span. auto src_span = UNSAFE_BUFFERS(pdfium::make_span(pSrcData, nSrcLen)); if (!m_pData) { m_pData = StringData::Create(src_span); return; } if (m_pData->CanOperateInPlace(m_pData->m_nDataLength + nSrcLen)) { m_pData->CopyContentsAt(m_pData->m_nDataLength, src_span); m_pData->m_nDataLength += nSrcLen; return; } // Increase size by at least 50% to amortize repeated concatenations. size_t nGrowLen = std::max(m_pData->m_nDataLength / 2, nSrcLen); RetainPtr pNewData = StringData::Create(m_pData->m_nDataLength + nGrowLen); pNewData->CopyContents(*m_pData); pNewData->CopyContentsAt(m_pData->m_nDataLength, src_span); pNewData->m_nDataLength = m_pData->m_nDataLength + nSrcLen; m_pData = std::move(pNewData); } template void StringTemplate::clear() { if (m_pData && m_pData->CanOperateInPlace(0)) { m_pData->m_nDataLength = 0; return; } m_pData.Reset(); } // Instantiate. template class StringTemplate; template class StringTemplate; } // namespace fxcrt