1 // Copyright 2024 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7 #include "core/fxcrt/string_template.h"
8
9 #include <algorithm>
10 #include <utility>
11
12 #include "core/fxcrt/check.h"
13 #include "core/fxcrt/check_op.h"
14 #include "core/fxcrt/compiler_specific.h"
15 #include "core/fxcrt/span.h"
16 #include "core/fxcrt/span_util.h"
17
18 namespace fxcrt {
19
20 template <typename T>
GetBuffer(size_t nMinBufLength)21 pdfium::span<T> StringTemplate<T>::GetBuffer(size_t nMinBufLength) {
22 if (!m_pData) {
23 if (nMinBufLength == 0) {
24 return pdfium::span<T>();
25 }
26 m_pData = StringData::Create(nMinBufLength);
27 m_pData->m_nDataLength = 0;
28 m_pData->m_String[0] = 0;
29 return m_pData->alloc_span();
30 }
31 if (m_pData->CanOperateInPlace(nMinBufLength)) {
32 return m_pData->alloc_span();
33 }
34 nMinBufLength = std::max(nMinBufLength, m_pData->m_nDataLength);
35 if (nMinBufLength == 0) {
36 return pdfium::span<T>();
37 }
38 RetainPtr<StringData> pNewData = StringData::Create(nMinBufLength);
39 pNewData->CopyContents(*m_pData);
40 pNewData->m_nDataLength = m_pData->m_nDataLength;
41 m_pData = std::move(pNewData);
42 return m_pData->alloc_span();
43 }
44
45 template <typename T>
ReleaseBuffer(size_t nNewLength)46 void StringTemplate<T>::ReleaseBuffer(size_t nNewLength) {
47 if (!m_pData) {
48 return;
49 }
50 nNewLength = std::min(nNewLength, m_pData->m_nAllocLength);
51 if (nNewLength == 0) {
52 clear();
53 return;
54 }
55 DCHECK_EQ(m_pData->m_nRefs, 1);
56 m_pData->m_nDataLength = nNewLength;
57 m_pData->capacity_span()[nNewLength] = 0;
58 if (m_pData->m_nAllocLength - nNewLength >= 32) {
59 // Over arbitrary threshold, so pay the price to relocate. Force copy to
60 // always occur by holding a second reference to the string.
61 StringTemplate preserve(*this);
62 ReallocBeforeWrite(nNewLength);
63 }
64 }
65
66 template <typename T>
Remove(T chRemove)67 size_t StringTemplate<T>::Remove(T chRemove) {
68 size_t count = std::count(span().begin(), span().end(), chRemove);
69 if (count == 0) {
70 return 0;
71 }
72 ReallocBeforeWrite(m_pData->m_nDataLength);
73 auto src_span = m_pData->span();
74 auto dst_span = m_pData->span();
75 // Perform self-intersecting copy in forwards order.
76 while (!src_span.empty()) {
77 if (src_span[0] != chRemove) {
78 dst_span[0] = src_span[0];
79 dst_span = dst_span.subspan(1);
80 }
81 src_span = src_span.subspan(1);
82 }
83 m_pData->m_nDataLength -= count;
84 m_pData->capacity_span()[m_pData->m_nDataLength] = 0;
85 return count;
86 }
87
88 template <typename T>
Insert(size_t index,T ch)89 size_t StringTemplate<T>::Insert(size_t index, T ch) {
90 const size_t cur_length = GetLength();
91 if (!IsValidLength(index)) {
92 return cur_length;
93 }
94 const size_t new_length = cur_length + 1;
95 ReallocBeforeWrite(new_length);
96 fxcrt::spanmove(m_pData->capacity_span().subspan(index + 1),
97 m_pData->capacity_span().subspan(index, new_length - index));
98 m_pData->capacity_span()[index] = ch;
99 m_pData->m_nDataLength = new_length;
100 return new_length;
101 }
102
103 template <typename T>
Delete(size_t index,size_t count)104 size_t StringTemplate<T>::Delete(size_t index, size_t count) {
105 if (!m_pData) {
106 return 0;
107 }
108 size_t old_length = m_pData->m_nDataLength;
109 if (count == 0 || index != std::clamp<size_t>(index, 0, old_length)) {
110 return old_length;
111 }
112 size_t removal_length = index + count;
113 if (removal_length > old_length) {
114 return old_length;
115 }
116 ReallocBeforeWrite(old_length);
117 // Include the NUL char not accounted for in lengths.
118 size_t chars_to_copy = old_length - removal_length + 1;
119 fxcrt::spanmove(
120 m_pData->capacity_span().subspan(index),
121 m_pData->capacity_span().subspan(removal_length, chars_to_copy));
122 m_pData->m_nDataLength = old_length - count;
123 return m_pData->m_nDataLength;
124 }
125
126 template <typename T>
SetAt(size_t index,T ch)127 void StringTemplate<T>::SetAt(size_t index, T ch) {
128 DCHECK(IsValidIndex(index));
129 ReallocBeforeWrite(m_pData->m_nDataLength);
130 m_pData->span()[index] = ch;
131 }
132
133 template <typename T>
Find(T ch,size_t start) const134 std::optional<size_t> StringTemplate<T>::Find(T ch, size_t start) const {
135 return Find(StringView(ch), start);
136 }
137
138 template <typename T>
Find(StringView str,size_t start) const139 std::optional<size_t> StringTemplate<T>::Find(StringView str,
140 size_t start) const {
141 if (!m_pData) {
142 return std::nullopt;
143 }
144 if (!IsValidIndex(start)) {
145 return std::nullopt;
146 }
147 std::optional<size_t> result =
148 spanpos(m_pData->span().subspan(start), str.span());
149 if (!result.has_value()) {
150 return std::nullopt;
151 }
152 return start + result.value();
153 }
154
155 template <typename T>
ReverseFind(T ch) const156 std::optional<size_t> StringTemplate<T>::ReverseFind(T ch) const {
157 if (!m_pData) {
158 return std::nullopt;
159 }
160 size_t nLength = m_pData->m_nDataLength;
161 while (nLength--) {
162 if (m_pData->span()[nLength] == ch) {
163 return nLength;
164 }
165 }
166 return std::nullopt;
167 }
168
169 template <typename T>
Replace(StringView oldstr,StringView newstr)170 size_t StringTemplate<T>::Replace(StringView oldstr, StringView newstr) {
171 if (!m_pData || oldstr.IsEmpty()) {
172 return 0;
173 }
174 size_t count = 0;
175 {
176 // Limit span lifetime.
177 pdfium::span<const T> search_span = m_pData->span();
178 while (true) {
179 std::optional<size_t> found = spanpos(search_span, oldstr.span());
180 if (!found.has_value()) {
181 break;
182 }
183 ++count;
184 search_span = search_span.subspan(found.value() + oldstr.GetLength());
185 }
186 }
187 if (count == 0) {
188 return 0;
189 }
190 size_t nNewLength = m_pData->m_nDataLength +
191 count * (newstr.GetLength() - oldstr.GetLength());
192 if (nNewLength == 0) {
193 clear();
194 return count;
195 }
196 RetainPtr<StringData> newstr_data = StringData::Create(nNewLength);
197 {
198 // Spans can't outlive StringData buffers.
199 pdfium::span<const T> search_span = m_pData->span();
200 pdfium::span<T> dest_span = newstr_data->span();
201 for (size_t i = 0; i < count; i++) {
202 size_t found = spanpos(search_span, oldstr.span()).value();
203 dest_span = spancpy(dest_span, search_span.first(found));
204 dest_span = spancpy(dest_span, newstr.span());
205 search_span = search_span.subspan(found + oldstr.GetLength());
206 }
207 dest_span = spancpy(dest_span, search_span);
208 CHECK(dest_span.empty());
209 }
210 m_pData = std::move(newstr_data);
211 return count;
212 }
213
214 template <typename T>
Trim(T ch)215 void StringTemplate<T>::Trim(T ch) {
216 TrimFront(ch);
217 TrimBack(ch);
218 }
219
220 template <typename T>
TrimFront(T ch)221 void StringTemplate<T>::TrimFront(T ch) {
222 TrimFront(StringView(ch));
223 }
224
225 template <typename T>
TrimBack(T ch)226 void StringTemplate<T>::TrimBack(T ch) {
227 TrimBack(StringView(ch));
228 }
229
230 template <typename T>
Trim(StringView targets)231 void StringTemplate<T>::Trim(StringView targets) {
232 TrimFront(targets);
233 TrimBack(targets);
234 }
235
236 template <typename T>
TrimFront(StringView targets)237 void StringTemplate<T>::TrimFront(StringView targets) {
238 if (!m_pData || targets.IsEmpty()) {
239 return;
240 }
241
242 size_t len = GetLength();
243 if (len == 0) {
244 return;
245 }
246
247 size_t pos = 0;
248 while (pos < len) {
249 size_t i = 0;
250 while (i < targets.GetLength() &&
251 targets.CharAt(i) != m_pData->span()[pos]) {
252 i++;
253 }
254 if (i == targets.GetLength()) {
255 break;
256 }
257 pos++;
258 }
259 if (!pos) {
260 return;
261 }
262
263 ReallocBeforeWrite(len);
264 size_t nDataLength = len - pos;
265 // Move the terminating NUL as well.
266 fxcrt::spanmove(m_pData->capacity_span(),
267 m_pData->capacity_span().subspan(pos, nDataLength + 1));
268 m_pData->m_nDataLength = nDataLength;
269 }
270
271 template <typename T>
TrimBack(StringView targets)272 void StringTemplate<T>::TrimBack(StringView targets) {
273 if (!m_pData || targets.IsEmpty()) {
274 return;
275 }
276
277 size_t pos = GetLength();
278 if (pos == 0) {
279 return;
280 }
281
282 while (pos) {
283 size_t i = 0;
284 while (i < targets.GetLength() &&
285 targets.CharAt(i) != m_pData->span()[pos - 1]) {
286 i++;
287 }
288 if (i == targets.GetLength()) {
289 break;
290 }
291 pos--;
292 }
293 if (pos < m_pData->m_nDataLength) {
294 ReallocBeforeWrite(m_pData->m_nDataLength);
295 m_pData->m_nDataLength = pos;
296 m_pData->capacity_span()[m_pData->m_nDataLength] = 0;
297 }
298 }
299
300 template <typename T>
ReallocBeforeWrite(size_t nNewLength)301 void StringTemplate<T>::ReallocBeforeWrite(size_t nNewLength) {
302 if (m_pData && m_pData->CanOperateInPlace(nNewLength)) {
303 return;
304 }
305 if (nNewLength == 0) {
306 clear();
307 return;
308 }
309 RetainPtr<StringData> pNewData = StringData::Create(nNewLength);
310 if (m_pData) {
311 size_t nCopyLength = std::min(m_pData->m_nDataLength, nNewLength);
312 // SAFETY: copy of no more than m_nDataLength bytes.
313 pNewData->CopyContents(
314 UNSAFE_BUFFERS(pdfium::make_span(m_pData->m_String, nCopyLength)));
315 pNewData->m_nDataLength = nCopyLength;
316 } else {
317 pNewData->m_nDataLength = 0;
318 }
319 pNewData->capacity_span()[pNewData->m_nDataLength] = 0;
320 m_pData = std::move(pNewData);
321 }
322
323 template <typename T>
AllocBeforeWrite(size_t nNewLength)324 void StringTemplate<T>::AllocBeforeWrite(size_t nNewLength) {
325 if (m_pData && m_pData->CanOperateInPlace(nNewLength)) {
326 return;
327 }
328 if (nNewLength == 0) {
329 clear();
330 return;
331 }
332 m_pData = StringData::Create(nNewLength);
333 }
334
335 template <typename T>
AssignCopy(const T * pSrcData,size_t nSrcLen)336 void StringTemplate<T>::AssignCopy(const T* pSrcData, size_t nSrcLen) {
337 AllocBeforeWrite(nSrcLen);
338 // SAFETY: AllocBeforeWrite() ensures `nSrcLen` bytes available.
339 m_pData->CopyContents(UNSAFE_BUFFERS(pdfium::make_span(pSrcData, nSrcLen)));
340 m_pData->m_nDataLength = nSrcLen;
341 }
342
343 template <typename T>
Concat(const T * pSrcData,size_t nSrcLen)344 void StringTemplate<T>::Concat(const T* pSrcData, size_t nSrcLen) {
345 if (!pSrcData || nSrcLen == 0) {
346 return;
347 }
348 // SAFETY: required from caller.
349 // TODO(tsepez): should be UNSAFE_BUFFER_USAGE or pass span.
350 auto src_span = UNSAFE_BUFFERS(pdfium::make_span(pSrcData, nSrcLen));
351 if (!m_pData) {
352 m_pData = StringData::Create(src_span);
353 return;
354 }
355 if (m_pData->CanOperateInPlace(m_pData->m_nDataLength + nSrcLen)) {
356 m_pData->CopyContentsAt(m_pData->m_nDataLength, src_span);
357 m_pData->m_nDataLength += nSrcLen;
358 return;
359 }
360 // Increase size by at least 50% to amortize repeated concatenations.
361 size_t nGrowLen = std::max(m_pData->m_nDataLength / 2, nSrcLen);
362 RetainPtr<StringData> pNewData =
363 StringData::Create(m_pData->m_nDataLength + nGrowLen);
364 pNewData->CopyContents(*m_pData);
365 pNewData->CopyContentsAt(m_pData->m_nDataLength, src_span);
366 pNewData->m_nDataLength = m_pData->m_nDataLength + nSrcLen;
367 m_pData = std::move(pNewData);
368 }
369
370 template <typename T>
clear()371 void StringTemplate<T>::clear() {
372 if (m_pData && m_pData->CanOperateInPlace(0)) {
373 m_pData->m_nDataLength = 0;
374 return;
375 }
376 m_pData.Reset();
377 }
378
379 // Instantiate.
380 template class StringTemplate<char>;
381 template class StringTemplate<wchar_t>;
382
383 } // namespace fxcrt
384