Mercurial > hg > pub > prymula > com
diff DPF-Prymula-audioplugins/dpf/distrho/extra/String.hpp @ 3:84e66ea83026
DPF-Prymula-audioplugins-0.231015-2
author | prymula <prymula76@outlook.com> |
---|---|
date | Mon, 16 Oct 2023 21:53:34 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DPF-Prymula-audioplugins/dpf/distrho/extra/String.hpp Mon Oct 16 21:53:34 2023 +0200 @@ -0,0 +1,1010 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2023 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DISTRHO_STRING_HPP_INCLUDED +#define DISTRHO_STRING_HPP_INCLUDED + +#include "../DistrhoUtils.hpp" +#include "../extra/ScopedSafeLocale.hpp" + +#include <algorithm> + +#if __cplusplus >= 201703L +# include <string_view> +#endif + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------- +// String class + +class String +{ +public: + // ------------------------------------------------------------------- + // constructors (no explicit conversions allowed) + + /* + * Empty string. + */ + explicit String() noexcept + : fBuffer(_null()), + fBufferLen(0), + fBufferAlloc(false) {} + + /* + * Simple character. + */ + explicit String(const char c) noexcept + : fBuffer(_null()), + fBufferLen(0), + fBufferAlloc(false) + { + char ch[2]; + ch[0] = c; + ch[1] = '\0'; + + _dup(ch); + } + + /* + * Simple char string. + */ + explicit String(char* const strBuf, const bool reallocData = true) noexcept + : fBuffer(_null()), + fBufferLen(0), + fBufferAlloc(false) + { + if (reallocData || strBuf == nullptr) + { + _dup(strBuf); + } + else + { + fBuffer = strBuf; + fBufferLen = std::strlen(strBuf); + fBufferAlloc = true; + } + } + + /* + * Simple const char string. + */ + explicit String(const char* const strBuf) noexcept + : fBuffer(_null()), + fBufferLen(0), + fBufferAlloc(false) + { + _dup(strBuf); + } + + #if __cplusplus >= 201703L + /* + * constexpr compatible variant. + */ + explicit constexpr String(const std::string_view& strView) noexcept + : fBuffer(const_cast<char*>(strView.data())), + fBufferLen(strView.size()), + fBufferAlloc(false) {} + #endif + + /* + * Integer. + */ + explicit String(const int value) noexcept + : fBuffer(_null()), + fBufferLen(0), + fBufferAlloc(false) + { + char strBuf[0xff+1]; + std::snprintf(strBuf, 0xff, "%d", value); + strBuf[0xff] = '\0'; + + _dup(strBuf); + } + + /* + * Unsigned integer, possibly in hexadecimal. + */ + explicit String(const unsigned int value, const bool hexadecimal = false) noexcept + : fBuffer(_null()), + fBufferLen(0), + fBufferAlloc(false) + { + char strBuf[0xff+1]; + std::snprintf(strBuf, 0xff, hexadecimal ? "0x%x" : "%u", value); + strBuf[0xff] = '\0'; + + _dup(strBuf); + } + + /* + * Long integer. + */ + explicit String(const long value) noexcept + : fBuffer(_null()), + fBufferLen(0), + fBufferAlloc(false) + { + char strBuf[0xff+1]; + std::snprintf(strBuf, 0xff, "%ld", value); + strBuf[0xff] = '\0'; + + _dup(strBuf); + } + + /* + * Long unsigned integer, possibly hexadecimal. + */ + explicit String(const unsigned long value, const bool hexadecimal = false) noexcept + : fBuffer(_null()), + fBufferLen(0), + fBufferAlloc(false) + { + char strBuf[0xff+1]; + std::snprintf(strBuf, 0xff, hexadecimal ? "0x%lx" : "%lu", value); + strBuf[0xff] = '\0'; + + _dup(strBuf); + } + + /* + * Long long integer. + */ + explicit String(const long long value) noexcept + : fBuffer(_null()), + fBufferLen(0), + fBufferAlloc(false) + { + char strBuf[0xff+1]; + std::snprintf(strBuf, 0xff, "%lld", value); + strBuf[0xff] = '\0'; + + _dup(strBuf); + } + + /* + * Long long unsigned integer, possibly hexadecimal. + */ + explicit String(const unsigned long long value, const bool hexadecimal = false) noexcept + : fBuffer(_null()), + fBufferLen(0), + fBufferAlloc(false) + { + char strBuf[0xff+1]; + std::snprintf(strBuf, 0xff, hexadecimal ? "0x%llx" : "%llu", value); + strBuf[0xff] = '\0'; + + _dup(strBuf); + } + + /* + * Single-precision floating point number. + */ + explicit String(const float value) noexcept + : fBuffer(_null()), + fBufferLen(0), + fBufferAlloc(false) + { + char strBuf[0xff+1]; + + { + const ScopedSafeLocale ssl; + std::snprintf(strBuf, 0xff, "%.12g", static_cast<double>(value)); + } + + strBuf[0xff] = '\0'; + + _dup(strBuf); + } + + /* + * Double-precision floating point number. + */ + explicit String(const double value) noexcept + : fBuffer(_null()), + fBufferLen(0), + fBufferAlloc(false) + { + char strBuf[0xff+1]; + + { + const ScopedSafeLocale ssl; + std::snprintf(strBuf, 0xff, "%.24g", value); + } + + strBuf[0xff] = '\0'; + + _dup(strBuf); + } + + // ------------------------------------------------------------------- + // non-explicit constructor + + /* + * Create string from another string. + */ + String(const String& str) noexcept + : fBuffer(_null()), + fBufferLen(0), + fBufferAlloc(false) + { + _dup(str.fBuffer); + } + + // ------------------------------------------------------------------- + // destructor + + /* + * Destructor. + */ + ~String() noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fBuffer != nullptr,); + + if (fBufferAlloc) + std::free(fBuffer); + + fBuffer = nullptr; + fBufferLen = 0; + fBufferAlloc = false; + } + + // ------------------------------------------------------------------- + // public methods + + /* + * Get length of the string. + */ + std::size_t length() const noexcept + { + return fBufferLen; + } + + /* + * Check if the string is empty. + */ + bool isEmpty() const noexcept + { + return (fBufferLen == 0); + } + + /* + * Check if the string is not empty. + */ + bool isNotEmpty() const noexcept + { + return (fBufferLen != 0); + } + + /* + * Check if the string contains a specific character, case-sensitive. + */ + bool contains(const char c) const noexcept + { + for (std::size_t i=0; i<fBufferLen; ++i) + { + if (fBuffer[i] == c) + return true; + } + + return false; + } + + /* + * Check if the string contains another string, optionally ignoring case. + */ + bool contains(const char* const strBuf, const bool ignoreCase = false) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(strBuf != nullptr, false); + + if (ignoreCase) + { +#ifdef __USE_GNU + return (strcasestr(fBuffer, strBuf) != nullptr); +#else + String tmp1(fBuffer), tmp2(strBuf); + + // memory allocation failed or empty string(s) + if (tmp1.fBuffer == _null() || tmp2.fBuffer == _null()) + return false; + + tmp1.toLower(); + tmp2.toLower(); + return (std::strstr(tmp1, tmp2) != nullptr); +#endif + } + + return (std::strstr(fBuffer, strBuf) != nullptr); + } + + /* + * Check if character at 'pos' is a digit. + */ + bool isDigit(const std::size_t pos) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(pos < fBufferLen, false); + + return (fBuffer[pos] >= '0' && fBuffer[pos] <= '9'); + } + + /* + * Check if the string starts with the character 'c'. + */ + bool startsWith(const char c) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(c != '\0', false); + + return (fBufferLen > 0 && fBuffer[0] == c); + } + + /* + * Check if the string starts with the string 'prefix'. + */ + bool startsWith(const char* const prefix) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(prefix != nullptr, false); + + const std::size_t prefixLen(std::strlen(prefix)); + + if (fBufferLen < prefixLen) + return false; + + return (std::strncmp(fBuffer, prefix, prefixLen) == 0); + } + + /* + * Check if the string ends with the character 'c'. + */ + bool endsWith(const char c) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(c != '\0', false); + + return (fBufferLen > 0 && fBuffer[fBufferLen-1] == c); + } + + /* + * Check if the string ends with the string 'suffix'. + */ + bool endsWith(const char* const suffix) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(suffix != nullptr, false); + + const std::size_t suffixLen(std::strlen(suffix)); + + if (fBufferLen < suffixLen) + return false; + + return (std::strncmp(fBuffer + (fBufferLen-suffixLen), suffix, suffixLen) == 0); + } + + /* + * Find the first occurrence of character 'c' in the string. + * Returns "length()" if the character is not found. + */ + std::size_t find(const char c, bool* const found = nullptr) const noexcept + { + if (fBufferLen == 0 || c == '\0') + { + if (found != nullptr) + *found = false; + return fBufferLen; + } + + for (std::size_t i=0; i < fBufferLen; ++i) + { + if (fBuffer[i] == c) + { + if (found != nullptr) + *found = true; + return i; + } + } + + if (found != nullptr) + *found = false; + return fBufferLen; + } + + /* + * Find the first occurrence of string 'strBuf' in the string. + * Returns "length()" if the string is not found. + */ + std::size_t find(const char* const strBuf, bool* const found = nullptr) const noexcept + { + if (fBufferLen == 0 || strBuf == nullptr || strBuf[0] == '\0') + { + if (found != nullptr) + *found = false; + return fBufferLen; + } + + if (char* const subStrBuf = std::strstr(fBuffer, strBuf)) + { + const ssize_t ret(subStrBuf - fBuffer); + + if (ret < 0) + { + // should never happen! + d_safe_assert_int("ret >= 0", __FILE__, __LINE__, int(ret)); + + if (found != nullptr) + *found = false; + return fBufferLen; + } + + if (found != nullptr) + *found = true; + return static_cast<std::size_t>(ret); + } + + if (found != nullptr) + *found = false; + return fBufferLen; + } + + /* + * Find the last occurrence of character 'c' in the string. + * Returns "length()" if the character is not found. + */ + std::size_t rfind(const char c, bool* const found = nullptr) const noexcept + { + if (fBufferLen == 0 || c == '\0') + { + if (found != nullptr) + *found = false; + return fBufferLen; + } + + for (std::size_t i=fBufferLen; i > 0; --i) + { + if (fBuffer[i-1] == c) + { + if (found != nullptr) + *found = true; + return i-1; + } + } + + if (found != nullptr) + *found = false; + return fBufferLen; + } + + /* + * Find the last occurrence of string 'strBuf' in the string. + * Returns "length()" if the string is not found. + */ + std::size_t rfind(const char* const strBuf, bool* const found = nullptr) const noexcept + { + if (found != nullptr) + *found = false; + + if (fBufferLen == 0 || strBuf == nullptr || strBuf[0] == '\0') + return fBufferLen; + + const std::size_t strBufLen(std::strlen(strBuf)); + + std::size_t ret = fBufferLen; + const char* tmpBuf = fBuffer; + + for (std::size_t i=0; i < fBufferLen; ++i) + { + if (std::strstr(tmpBuf+1, strBuf) == nullptr && std::strncmp(tmpBuf, strBuf, strBufLen) == 0) + { + if (found != nullptr) + *found = true; + break; + } + + --ret; + ++tmpBuf; + } + + return fBufferLen-ret; + } + + /* + * Clear the string. + */ + void clear() noexcept + { + truncate(0); + } + + /* + * Replace all occurrences of character 'before' with character 'after'. + */ + String& replace(const char before, const char after) noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(before != '\0' /* && after != '\0' */, *this); + + for (std::size_t i=0; i < fBufferLen; ++i) + { + if (fBuffer[i] == before) + fBuffer[i] = after; + } + + return *this; + } + + /* + * Remove all occurrences of character 'c', shifting and truncating the string as necessary. + */ + String& remove(const char c) noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(c != '\0', *this); + + if (fBufferLen == 0) + return *this; + + for (std::size_t i=0; i < fBufferLen; ++i) + { + if (fBuffer[i] == c) + { + --fBufferLen; + std::memmove(fBuffer+i, fBuffer+i+1, fBufferLen-i); + } + } + + fBuffer[fBufferLen] = '\0'; + return *this; + } + + /* + * Truncate the string to size 'n'. + */ + String& truncate(const std::size_t n) noexcept + { + if (n >= fBufferLen) + return *this; + + fBuffer[n] = '\0'; + fBufferLen = n; + + return *this; + } + + /* + * Convert all non-basic characters to '_'. + */ + String& toBasic() noexcept + { + for (std::size_t i=0; i < fBufferLen; ++i) + { + if (fBuffer[i] >= '0' && fBuffer[i] <= '9') + continue; + if (fBuffer[i] >= 'A' && fBuffer[i] <= 'Z') + continue; + if (fBuffer[i] >= 'a' && fBuffer[i] <= 'z') + continue; + if (fBuffer[i] == '_') + continue; + + fBuffer[i] = '_'; + } + + return *this; + } + + /* + * Convert all ascii characters to lowercase. + */ + String& toLower() noexcept + { + static const char kCharDiff('a' - 'A'); + + for (std::size_t i=0; i < fBufferLen; ++i) + { + if (fBuffer[i] >= 'A' && fBuffer[i] <= 'Z') + fBuffer[i] = static_cast<char>(fBuffer[i] + kCharDiff); + } + + return *this; + } + + /* + * Convert all ascii characters to uppercase. + */ + String& toUpper() noexcept + { + static const char kCharDiff('a' - 'A'); + + for (std::size_t i=0; i < fBufferLen; ++i) + { + if (fBuffer[i] >= 'a' && fBuffer[i] <= 'z') + fBuffer[i] = static_cast<char>(fBuffer[i] - kCharDiff); + } + + return *this; + } + + /* + * Create a new string where all non-basic characters are converted to '_'. + * @see toBasic() + */ + String asBasic() const noexcept + { + String s(*this); + return s.toBasic(); + } + + /* + * Create a new string where all ascii characters are converted lowercase. + * @see toLower() + */ + String asLower() const noexcept + { + String s(*this); + return s.toLower(); + } + + /* + * Create a new string where all ascii characters are converted to uppercase. + * @see toUpper() + */ + String asUpper() const noexcept + { + String s(*this); + return s.toUpper(); + } + + /* + * Direct access to the string buffer (read-only). + */ + const char* buffer() const noexcept + { + return fBuffer; + } + + /* + * Get and release the string buffer, while also clearing this string. + * This allows to keep a pointer to the buffer after this object is deleted. + * Result must be freed. + */ + char* getAndReleaseBuffer() noexcept + { + char* ret = fBufferLen > 0 ? fBuffer : nullptr; + fBuffer = _null(); + fBufferLen = 0; + fBufferAlloc = false; + return ret; + } + + // ------------------------------------------------------------------- + // base64 stuff, based on http://www.adp-gmbh.ch/cpp/common/base64.html + // Copyright (C) 2004-2008 René Nyffenegger + + static String asBase64(const void* const data, const std::size_t dataSize) + { + static const char* const kBase64Chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +#ifndef _MSC_VER + const std::size_t kTmpBufSize = std::min(d_nextPowerOf2(static_cast<uint32_t>(dataSize/3)), 65536U); +#else + constexpr std::size_t kTmpBufSize = 65536U; +#endif + + const uchar* bytesToEncode((const uchar*)data); + + uint i=0, j=0; + uint charArray3[3], charArray4[4]; + + char strBuf[kTmpBufSize + 1]; + strBuf[kTmpBufSize] = '\0'; + std::size_t strBufIndex = 0; + + String ret; + + for (std::size_t s=0; s<dataSize; ++s) + { + charArray3[i++] = *(bytesToEncode++); + + if (i == 3) + { + charArray4[0] = (charArray3[0] & 0xfc) >> 2; + charArray4[1] = ((charArray3[0] & 0x03) << 4) + ((charArray3[1] & 0xf0) >> 4); + charArray4[2] = ((charArray3[1] & 0x0f) << 2) + ((charArray3[2] & 0xc0) >> 6); + charArray4[3] = charArray3[2] & 0x3f; + + for (i=0; i<4; ++i) + strBuf[strBufIndex++] = kBase64Chars[charArray4[i]]; + + if (strBufIndex >= kTmpBufSize-7) + { + strBuf[strBufIndex] = '\0'; + strBufIndex = 0; + ret += strBuf; + } + + i = 0; + } + } + + if (i != 0) + { + for (j=i; j<3; ++j) + charArray3[j] = '\0'; + + charArray4[0] = (charArray3[0] & 0xfc) >> 2; + charArray4[1] = ((charArray3[0] & 0x03) << 4) + ((charArray3[1] & 0xf0) >> 4); + charArray4[2] = ((charArray3[1] & 0x0f) << 2) + ((charArray3[2] & 0xc0) >> 6); + charArray4[3] = charArray3[2] & 0x3f; + + for (j=0; j<4 && i<3 && j<i+1; ++j) + strBuf[strBufIndex++] = kBase64Chars[charArray4[j]]; + + for (; i++ < 3;) + strBuf[strBufIndex++] = '='; + } + + if (strBufIndex != 0) + { + strBuf[strBufIndex] = '\0'; + ret += strBuf; + } + + return ret; + } + + // ------------------------------------------------------------------- + // public operators + + operator const char*() const noexcept + { + return fBuffer; + } + + char operator[](const std::size_t pos) const noexcept + { + if (pos < fBufferLen) + return fBuffer[pos]; + + d_safe_assert("pos < fBufferLen", __FILE__, __LINE__); + + static char fallback; + fallback = '\0'; + return fallback; + } + + char& operator[](const std::size_t pos) noexcept + { + if (pos < fBufferLen) + return fBuffer[pos]; + + d_safe_assert("pos < fBufferLen", __FILE__, __LINE__); + + static char fallback; + fallback = '\0'; + return fallback; + } + + bool operator==(const char* const strBuf) const noexcept + { + return (strBuf != nullptr && std::strcmp(fBuffer, strBuf) == 0); + } + + bool operator==(const String& str) const noexcept + { + return operator==(str.fBuffer); + } + + bool operator!=(const char* const strBuf) const noexcept + { + return !operator==(strBuf); + } + + bool operator!=(const String& str) const noexcept + { + return !operator==(str.fBuffer); + } + + String& operator=(const char* const strBuf) noexcept + { + _dup(strBuf); + + return *this; + } + + String& operator=(const String& str) noexcept + { + _dup(str.fBuffer); + + return *this; + } + + String& operator+=(const char* const strBuf) noexcept + { + if (strBuf == nullptr || strBuf[0] == '\0') + return *this; + + const std::size_t strBufLen = std::strlen(strBuf); + + // for empty strings, we can just take the appended string as our entire data + if (isEmpty()) + { + _dup(strBuf, strBufLen); + return *this; + } + + // we have some data ourselves, reallocate to add the new stuff + char* const newBuf = (char*)realloc(fBuffer, fBufferLen + strBufLen + 1); + DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, *this); + + std::memcpy(newBuf + fBufferLen, strBuf, strBufLen + 1); + + fBuffer = newBuf; + fBufferLen += strBufLen; + + return *this; + } + + String& operator+=(const String& str) noexcept + { + return operator+=(str.fBuffer); + } + + String operator+(const char* const strBuf) noexcept + { + if (strBuf == nullptr || strBuf[0] == '\0') + return *this; + if (isEmpty()) + return String(strBuf); + + const std::size_t strBufLen = std::strlen(strBuf); + const std::size_t newBufSize = fBufferLen + strBufLen; + char* const newBuf = (char*)malloc(newBufSize + 1); + DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, String()); + + std::memcpy(newBuf, fBuffer, fBufferLen); + std::memcpy(newBuf + fBufferLen, strBuf, strBufLen + 1); + + return String(newBuf, false); + } + + String operator+(const String& str) noexcept + { + return operator+(str.fBuffer); + } + + // needed for std::map compatibility + bool operator<(const String& str) const noexcept + { + return std::strcmp(fBuffer, str.fBuffer) < 0; + } + + // ------------------------------------------------------------------- + +private: + char* fBuffer; // the actual string buffer + std::size_t fBufferLen; // string length + bool fBufferAlloc; // wherever the buffer is allocated, not using _null() + + /* + * Static null string. + * Prevents allocation for new and/or empty strings. + */ + static char* _null() noexcept + { + static char sNull = '\0'; + return &sNull; + } + + /* + * Helper function. + * Called whenever the string needs to be allocated. + * + * Notes: + * - Allocates string only if 'strBuf' is not null and new string contents are different + * - If 'strBuf' is null, 'size' must be 0 + */ + void _dup(const char* const strBuf, const std::size_t size = 0) noexcept + { + if (strBuf != nullptr) + { + // don't recreate string if contents match + if (std::strcmp(fBuffer, strBuf) == 0) + return; + + if (fBufferAlloc) + std::free(fBuffer); + + fBufferLen = (size > 0) ? size : std::strlen(strBuf); + fBuffer = (char*)std::malloc(fBufferLen+1); + + if (fBuffer == nullptr) + { + fBuffer = _null(); + fBufferLen = 0; + fBufferAlloc = false; + return; + } + + fBufferAlloc = true; + + std::strcpy(fBuffer, strBuf); + fBuffer[fBufferLen] = '\0'; + } + else + { + DISTRHO_SAFE_ASSERT_UINT(size == 0, static_cast<uint>(size)); + + // don't recreate null string + if (! fBufferAlloc) + return; + + DISTRHO_SAFE_ASSERT(fBuffer != nullptr); + std::free(fBuffer); + + fBuffer = _null(); + fBufferLen = 0; + fBufferAlloc = false; + } + } + + DISTRHO_PREVENT_HEAP_ALLOCATION +}; + +// ----------------------------------------------------------------------- + +static inline +String operator+(const String& strBefore, const char* const strBufAfter) noexcept +{ + if (strBufAfter == nullptr || strBufAfter[0] == '\0') + return strBefore; + if (strBefore.isEmpty()) + return String(strBufAfter); + + const std::size_t strBeforeLen = strBefore.length(); + const std::size_t strBufAfterLen = std::strlen(strBufAfter); + const std::size_t newBufSize = strBeforeLen + strBufAfterLen; + char* const newBuf = (char*)malloc(newBufSize + 1); + DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, String()); + + std::memcpy(newBuf, strBefore.buffer(), strBeforeLen); + std::memcpy(newBuf + strBeforeLen, strBufAfter, strBufAfterLen + 1); + + return String(newBuf, false); +} + +static inline +String operator+(const char* const strBufBefore, const String& strAfter) noexcept +{ + if (strAfter.isEmpty()) + return String(strBufBefore); + if (strBufBefore == nullptr || strBufBefore[0] == '\0') + return strAfter; + + const std::size_t strBufBeforeLen = std::strlen(strBufBefore); + const std::size_t strAfterLen = strAfter.length(); + const std::size_t newBufSize = strBufBeforeLen + strAfterLen; + char* const newBuf = (char*)malloc(newBufSize + 1); + DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, String()); + + std::memcpy(newBuf, strBufBefore, strBufBeforeLen); + std::memcpy(newBuf + strBufBeforeLen, strAfter.buffer(), strAfterLen + 1); + + return String(newBuf, false); +} + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO + +#endif // DISTRHO_STRING_HPP_INCLUDED