diff --git a/automation/tests/aegisub.cpp b/automation/tests/aegisub.cpp index 61330b4a10..a7d4ab46f6 100644 --- a/automation/tests/aegisub.cpp +++ b/automation/tests/aegisub.cpp @@ -20,8 +20,8 @@ #include #include +#include -#include #include #include @@ -36,7 +36,7 @@ void check(lua_State *L, int status) { } int close_and_exit(lua_State *L) { - int status = lua_tointeger(L, 1); + int status = (int)lua_tointeger(L, 1); lua_close(L); exit(status); } @@ -48,7 +48,7 @@ int main(int argc, char **argv) { return 1; } - std::locale::global(boost::locale::generator().generate("")); + agi::util::InitLocale(); agi::dispatch::Init([](agi::dispatch::Thunk f) { }); agi::log::log = new agi::log::LogSink; diff --git a/libaegisub/ass/dialogue_parser.cpp b/libaegisub/ass/dialogue_parser.cpp index ea486dcf71..b11ebb1a0d 100644 --- a/libaegisub/ass/dialogue_parser.cpp +++ b/libaegisub/ass/dialogue_parser.cpp @@ -16,22 +16,20 @@ #include "libaegisub/ass/dialogue_parser.h" +#include "libaegisub/exception.h" #include "libaegisub/spellchecker.h" - -#include -#include -#include +#include "libaegisub/unicode.h" namespace { -typedef std::vector TokenVec; +using TokenVec = std::vector; using namespace agi::ass; namespace dt = DialogueTokenType; namespace ss = SyntaxStyle; class SyntaxHighlighter { TokenVec ranges; - std::string const& text; + std::string_view text; agi::SpellChecker *spellchecker; void SetStyling(size_t len, int type) { @@ -42,7 +40,7 @@ class SyntaxHighlighter { } public: - SyntaxHighlighter(std::string const& text, agi::SpellChecker *spellchecker) + SyntaxHighlighter(std::string_view text, agi::SpellChecker *spellchecker) : text(text) , spellchecker(spellchecker) { } @@ -91,7 +89,7 @@ class SyntaxHighlighter { }; class WordSplitter { - std::string const& text; + std::string_view text; std::vector &tokens; size_t pos = 0; @@ -107,19 +105,26 @@ class WordSplitter { } void SplitText(size_t &i) { - using namespace boost::locale::boundary; - ssegment_index map(word, text.begin() + pos, text.begin() + pos + tokens[i].length); - for (auto const& segment : map) { - auto len = static_cast(distance(begin(segment), end(segment))); - if (segment.rule() & word_letters) + UErrorCode err = U_ZERO_ERROR; + thread_local std::unique_ptr + bi(icu::BreakIterator::createWordInstance(icu::Locale::getDefault(), err)); + agi::UTextPtr ut(utext_openUTF8(nullptr, text.data() + pos, tokens[i].length, &err)); + bi->setText(ut.get(), err); + if (U_FAILURE(err)) throw agi::InternalError(u_errorName(err)); + size_t pos = 0; + while (bi->next() != UBRK_DONE) { + auto len = bi->current() - pos; + auto rule = bi->getRuleStatus(); // FIXME: getRuleStatusVec? + if (rule >= UBRK_WORD_LETTER && rule < UBRK_WORD_KANA_LIMIT) SwitchTo(i, dt::WORD, len); else SwitchTo(i, dt::TEXT, len); + pos = bi->current(); } } public: - WordSplitter(std::string const& text, std::vector &tokens) + WordSplitter(std::string_view text, std::vector &tokens) : text(text) , tokens(tokens) { } @@ -137,14 +142,15 @@ class WordSplitter { }; } -namespace agi { -namespace ass { +namespace agi::ass { -std::vector SyntaxHighlight(std::string const& text, std::vector const& tokens, SpellChecker *spellchecker) { +std::vector SyntaxHighlight(std::string_view text, + std::vector const& tokens, + SpellChecker *spellchecker) { return SyntaxHighlighter(text, spellchecker).Highlight(tokens); } -void MarkDrawings(std::string const& str, std::vector &tokens) { +void MarkDrawings(std::string_view str, std::vector &tokens) { if (tokens.empty()) return; size_t last_ovr_end = 0; @@ -209,10 +215,8 @@ void MarkDrawings(std::string const& str, std::vector &tokens) { } } -void SplitWords(std::string const& str, std::vector &tokens) { +void SplitWords(std::string_view str, std::vector &tokens) { MarkDrawings(str, tokens); WordSplitter(str, tokens).SplitWords(); } - -} } diff --git a/libaegisub/ass/karaoke.cpp b/libaegisub/ass/karaoke.cpp new file mode 100644 index 0000000000..e1795f7105 --- /dev/null +++ b/libaegisub/ass/karaoke.cpp @@ -0,0 +1,205 @@ +// Copyright (c) 2022, Thomas Goyne +// +// Permission to use, copy, modify, and 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. +// +// Aegisub Project http://www.aegisub.org/ + +#include + +#include +#include + +#include +#include + +namespace agi::ass { +std::string KaraokeSyllable::GetText(bool k_tag) const { + std::string ret; + + if (k_tag) + ret = agi::format("{%s%d}", tag_type, ((duration + 5) / 10)); + + std::string_view sv = text; + size_t idx = 0; + for (auto const& ovr : ovr_tags) { + ret += sv.substr(idx, ovr.first - idx); + ret += ovr.second; + idx = ovr.first; + } + ret += sv.substr(idx); + return ret; +} + +void Karaoke::SetLine(std::vector&& syls, bool auto_split, std::optional end_time) { + this->syls = std::move(syls); + + if (end_time) { + Normalize(*end_time); + } + + // Add karaoke splits at each space + if (auto_split && size() == 1) { + AutoSplit(); + } + + AnnounceSyllablesChanged(); +} + +void Karaoke::Normalize(int end_time) { + auto& last_syl = syls.back(); + int last_end = last_syl.start_time + last_syl.duration; + + // Total duration is shorter than the line length so just extend the last + // syllable; this has no effect on rendering but is easier to work with + if (last_end < end_time) + last_syl.duration += end_time - last_end; + else if (last_end > end_time) { + // Truncate any syllables that extend past the end of the line + for (auto& syl : syls) { + if (syl.start_time > end_time) { + syl.start_time = end_time; + syl.duration = 0; + } + else { + syl.duration = std::min(syl.duration, end_time - syl.start_time); + } + } + } +} + +void Karaoke::AutoSplit() { + size_t pos; + while ((pos = syls.back().text.find(' ')) != std::string::npos) + DoAddSplit(syls.size() - 1, pos + 1); +} + +std::string Karaoke::GetText() const { + std::string text; + text.reserve(size() * 10); + + for (auto const& syl : syls) + text += syl.GetText(true); + + return text; +} + +std::string_view Karaoke::GetTagType() const { + return begin()->tag_type; +} + +void Karaoke::SetTagType(std::string_view new_type) { + for (auto& syl : syls) + syl.tag_type = new_type; +} + +void Karaoke::DoAddSplit(size_t syl_idx, size_t pos) { + syls.insert(syls.begin() + syl_idx + 1, KaraokeSyllable()); + KaraokeSyllable &syl = syls[syl_idx]; + KaraokeSyllable &new_syl = syls[syl_idx + 1]; + + // If the syl is empty or the user is adding a syllable past the last + // character then pos will be out of bounds. Doing this is a bit goofy, + // but it's sometimes required for complex karaoke scripts + if (pos < syl.text.size()) { + new_syl.text = syl.text.substr(pos); + syl.text = syl.text.substr(0, pos); + } + + if (new_syl.text.empty()) + new_syl.duration = 0; + else if (syl.text.empty()) { + new_syl.duration = syl.duration; + syl.duration = 0; + } + else { + new_syl.duration = (syl.duration * new_syl.text.size() / (syl.text.size() + new_syl.text.size()) + 5) / 10 * 10; + syl.duration -= new_syl.duration; + } + + assert(syl.duration >= 0); + + new_syl.start_time = syl.start_time + syl.duration; + new_syl.tag_type = syl.tag_type; + + // Move all override tags after the split to the new syllable and fix the indices + size_t text_len = syl.text.size(); + for (auto it = syl.ovr_tags.begin(); it != syl.ovr_tags.end(); ) { + if (it->first < text_len) + ++it; + else { + new_syl.ovr_tags[it->first - text_len] = it->second; + syl.ovr_tags.erase(it++); + } + } +} + +void Karaoke::AddSplit(size_t syl_idx, size_t pos) { + DoAddSplit(syl_idx, pos); + AnnounceSyllablesChanged(); +} + +void Karaoke::RemoveSplit(size_t syl_idx) { + // Don't allow removing the first syllable + if (syl_idx == 0) return; + + KaraokeSyllable &syl = syls[syl_idx]; + KaraokeSyllable &prev = syls[syl_idx - 1]; + + prev.duration += syl.duration; + for (auto const& tag : syl.ovr_tags) + prev.ovr_tags[tag.first + prev.text.size()] = tag.second; + prev.text += syl.text; + + syls.erase(syls.begin() + syl_idx); + + AnnounceSyllablesChanged(); +} + +void Karaoke::SetStartTime(size_t syl_idx, int time) { + // Don't allow moving the first syllable + if (syl_idx == 0) return; + + KaraokeSyllable &syl = syls[syl_idx]; + KaraokeSyllable &prev = syls[syl_idx - 1]; + + assert(time >= prev.start_time); + assert(time <= syl.start_time + syl.duration); + + int delta = time - syl.start_time; + syl.start_time = time; + syl.duration -= delta; + prev.duration += delta; +} + +void Karaoke::SetLineTimes(int start_time, int end_time) { + assert(end_time >= start_time); + + size_t idx = 0; + // Chop off any portion of syllables starting before the new start_time + do { + int delta = start_time - syls[idx].start_time; + syls[idx].start_time = start_time; + syls[idx].duration = std::max(0, syls[idx].duration - delta); + } while (++idx < syls.size() && syls[idx].start_time < start_time); + + // And truncate any syllables ending after the new end_time + idx = syls.size() - 1; + while (syls[idx].start_time > end_time) { + syls[idx].start_time = end_time; + syls[idx].duration = 0; + --idx; + } + syls[idx].duration = end_time - syls[idx].start_time; +} + +} // namespace agi::ass diff --git a/libaegisub/ass/time.cpp b/libaegisub/ass/time.cpp index b85f48a1f6..07367d229c 100644 --- a/libaegisub/ass/time.cpp +++ b/libaegisub/ass/time.cpp @@ -26,7 +26,7 @@ namespace agi { Time::Time(int time) : time(util::mid(0, time, 10 * 60 * 60 * 1000 - 6)) { } -Time::Time(std::string const& text) { +Time::Time(std::string_view text) { int after_decimal = -1; int current = 0; for (char c : text) { diff --git a/libaegisub/ass/uuencode.cpp b/libaegisub/ass/uuencode.cpp index 182472926c..893268d077 100644 --- a/libaegisub/ass/uuencode.cpp +++ b/libaegisub/ass/uuencode.cpp @@ -24,7 +24,7 @@ // characters, and files with non-multiple-of-three lengths are padded with // zero. -namespace agi { namespace ass { +namespace agi::ass { std::string UUEncode(const char *begin, const char *end, bool insert_linebreaks) { size_t size = std::distance(begin, end); @@ -82,4 +82,4 @@ std::vector UUDecode(const char *begin, const char *end) { return ret; } -} } +} diff --git a/libaegisub/audio/provider.cpp b/libaegisub/audio/provider.cpp index 961e5bf89c..c3598e08a6 100644 --- a/libaegisub/audio/provider.cpp +++ b/libaegisub/audio/provider.cpp @@ -81,7 +81,7 @@ class writer { std::ostream& out; public: - writer(agi::fs::path const& filename) : outfile(filename, true), out(outfile.Get()) { } + writer(std::filesystem::path const& filename) : outfile(filename, true), out(outfile.Get()) { } template void write(const char(&str)[N]) { diff --git a/libaegisub/audio/provider_convert.cpp b/libaegisub/audio/provider_convert.cpp index b45d8a852a..0feb2377d7 100644 --- a/libaegisub/audio/provider_convert.cpp +++ b/libaegisub/audio/provider_convert.cpp @@ -17,7 +17,6 @@ #include "libaegisub/audio/provider.h" #include -#include #include @@ -178,25 +177,25 @@ std::unique_ptr CreateConvertAudioProvider(std::unique_ptrAreSamplesFloat()) { LOG_D("audio_provider") << "Converting float to S16"; if (provider->GetBytesPerSample() == sizeof(float)) - provider = agi::make_unique>(std::move(provider)); + provider = std::make_unique>(std::move(provider)); else - provider = agi::make_unique>(std::move(provider)); + provider = std::make_unique>(std::move(provider)); } if (provider->GetBytesPerSample() != 2) { LOG_D("audio_provider") << "Converting " << provider->GetBytesPerSample() << " bytes per sample or wrong endian to S16"; - provider = agi::make_unique>(std::move(provider)); + provider = std::make_unique>(std::move(provider)); } // We currently only support mono audio if (provider->GetChannels() != 1) { LOG_D("audio_provider") << "Downmixing to mono from " << provider->GetChannels() << " channels"; - provider = agi::make_unique(std::move(provider)); + provider = std::make_unique(std::move(provider)); } // Some players don't like low sample rate audio while (provider->GetSampleRate() < 32000) { LOG_D("audio_provider") << "Doubling sample rate"; - provider = agi::make_unique(std::move(provider)); + provider = std::make_unique(std::move(provider)); } return provider; diff --git a/libaegisub/audio/provider_dummy.cpp b/libaegisub/audio/provider_dummy.cpp index 3e81ffb57d..3e7f104ef0 100644 --- a/libaegisub/audio/provider_dummy.cpp +++ b/libaegisub/audio/provider_dummy.cpp @@ -17,9 +17,7 @@ #include "libaegisub/audio/provider.h" #include "libaegisub/fs.h" -#include "libaegisub/make_unique.h" -#include #include /* @@ -60,8 +58,8 @@ class DummyAudioProvider final : public AudioProvider { } public: - DummyAudioProvider(agi::fs::path const& uri) { - noise = boost::contains(uri.string(), ":noise?"); + DummyAudioProvider(std::filesystem::path const& uri) { + noise = uri.string().find(":noise?") != std::string::npos; channels = 1; sample_rate = 44100; bytes_per_sample = 2; @@ -72,9 +70,9 @@ class DummyAudioProvider final : public AudioProvider { } namespace agi { -std::unique_ptr CreateDummyAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *) { - if (!boost::starts_with(file.string(), "dummy-audio:")) +std::unique_ptr CreateDummyAudioProvider(std::filesystem::path const& file, agi::BackgroundRunner *) { + if (!file.string().starts_with("dummy-audio:")) return {}; - return agi::make_unique(file); + return std::make_unique(file); } } diff --git a/libaegisub/audio/provider_hd.cpp b/libaegisub/audio/provider_hd.cpp index 19e33eeed6..730a6534df 100644 --- a/libaegisub/audio/provider_hd.cpp +++ b/libaegisub/audio/provider_hd.cpp @@ -20,15 +20,15 @@ #include #include #include -#include -#include +#include #include #include #include namespace { using namespace agi; +using namespace std::filesystem; class HDAudioProvider final : public AudioProviderWrapper { mutable temp_file_mapping file; @@ -49,7 +49,7 @@ class HDAudioProvider final : public AudioProviderWrapper { } } - fs::path CacheFilename(fs::path const& dir) { + path CacheFilename(path const& dir) { // Check free space if ((uint64_t)num_samples * bytes_per_sample > fs::FreeSpace(dir)) throw AudioProviderError("Not enough free disk space in " + dir.string() + " to cache the audio"); @@ -59,7 +59,7 @@ class HDAudioProvider final : public AudioProviderWrapper { } public: - HDAudioProvider(std::unique_ptr src, agi::fs::path const& dir) + HDAudioProvider(std::unique_ptr src, path const& dir) : AudioProviderWrapper(std::move(src)) , file(dir / CacheFilename(dir), num_samples * bytes_per_sample) { @@ -75,7 +75,7 @@ class HDAudioProvider final : public AudioProviderWrapper { }); } - ~HDAudioProvider() { + ~HDAudioProvider() override { cancelled = true; decoder.join(); } @@ -83,7 +83,7 @@ class HDAudioProvider final : public AudioProviderWrapper { } namespace agi { -std::unique_ptr CreateHDAudioProvider(std::unique_ptr src, agi::fs::path const& dir) { - return agi::make_unique(std::move(src), dir); +std::unique_ptr CreateHDAudioProvider(std::unique_ptr src, std::filesystem::path const& dir) { + return std::make_unique(std::move(src), dir); } } diff --git a/libaegisub/audio/provider_lock.cpp b/libaegisub/audio/provider_lock.cpp index eb397e4103..927eea7673 100644 --- a/libaegisub/audio/provider_lock.cpp +++ b/libaegisub/audio/provider_lock.cpp @@ -16,7 +16,6 @@ #include "libaegisub/audio/provider.h" -#include #include @@ -39,6 +38,6 @@ class LockAudioProvider final : public agi::AudioProviderWrapper { namespace agi { std::unique_ptr CreateLockAudioProvider(std::unique_ptr src) { - return agi::make_unique(std::move(src)); + return std::make_unique(std::move(src)); } } diff --git a/libaegisub/audio/provider_pcm.cpp b/libaegisub/audio/provider_pcm.cpp index 4131ecd2d2..9ca26a8491 100644 --- a/libaegisub/audio/provider_pcm.cpp +++ b/libaegisub/audio/provider_pcm.cpp @@ -18,7 +18,6 @@ #include "libaegisub/file_mapping.h" #include "libaegisub/fs.h" -#include "libaegisub/make_unique.h" #include #include @@ -106,7 +105,7 @@ struct RiffWav { static uint32_t chunk_size(uint32_t size) { return size; } }; -typedef std::array GUID; +using GUID = std::array; static const GUID w64GuidRIFF = {{ // {66666972-912E-11CF-A5D6-28DB04C10000} @@ -219,7 +218,7 @@ std::unique_ptr CreatePCMAudioProvider(fs::path const& filename, std::string msg; try { - return make_unique>(filename); + return std::make_unique>(filename); } catch (AudioDataNotFound const& err) { msg = "RIFF PCM WAV audio provider: " + err.GetMessage(); @@ -230,7 +229,7 @@ std::unique_ptr CreatePCMAudioProvider(fs::path const& filename, } try { - return make_unique>(filename); + return std::make_unique>(filename); } catch (AudioDataNotFound const& err) { msg += "\nWave64 audio provider: " + err.GetMessage(); @@ -242,7 +241,6 @@ std::unique_ptr CreatePCMAudioProvider(fs::path const& filename, if (wrong_file_type) throw AudioDataNotFound(msg); - else - throw AudioProviderError(msg); + throw AudioProviderError(msg); } } diff --git a/libaegisub/audio/provider_ram.cpp b/libaegisub/audio/provider_ram.cpp index 0c1da546c1..c4acbbdc11 100644 --- a/libaegisub/audio/provider_ram.cpp +++ b/libaegisub/audio/provider_ram.cpp @@ -16,7 +16,6 @@ #include "libaegisub/audio/provider.h" -#include "libaegisub/make_unique.h" #include #include @@ -63,7 +62,7 @@ class RAMAudioProvider final : public AudioProviderWrapper { }); } - ~RAMAudioProvider() { + ~RAMAudioProvider() override { cancelled = true; decoder.join(); } @@ -77,9 +76,9 @@ void RAMAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const break; } - const int i = (start * bytes_per_sample) >> CacheBits; - const int start_offset = (start * bytes_per_sample) & (CacheBlockSize-1); - const int read_size = std::min(bytes_remaining, CacheBlockSize - start_offset); + const int64_t i = (start * bytes_per_sample) >> CacheBits; + const int64_t start_offset = (start * bytes_per_sample) & (CacheBlockSize-1); + const int64_t read_size = std::min(bytes_remaining, CacheBlockSize - start_offset); memcpy(charbuf, &blockcache[i][start_offset], read_size); charbuf += read_size; @@ -91,6 +90,6 @@ void RAMAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const namespace agi { std::unique_ptr CreateRAMAudioProvider(std::unique_ptr src) { - return agi::make_unique(std::move(src)); + return std::make_unique(std::move(src)); } } diff --git a/libaegisub/common/cajun/elements.cpp b/libaegisub/common/cajun/elements.cpp index b8f5a01744..a6d41f8d20 100644 --- a/libaegisub/common/cajun/elements.cpp +++ b/libaegisub/common/cajun/elements.cpp @@ -36,7 +36,7 @@ struct CastVisitor final : public CastVisitorBase { namespace json { class UnknownElement::Imp { public: - virtual ~Imp() {} + virtual ~Imp() = default; virtual void Accept(ConstVisitor& visitor) const = 0; virtual void Accept(Visitor& visitor) = 0; }; @@ -50,18 +50,20 @@ class Imp_T final : public UnknownElement::Imp { public: Imp_T(ElementTypeT element) : m_Element(std::move(element)) { } - void Accept(ConstVisitor& visitor) const { visitor.Visit(m_Element); } - void Accept(Visitor& visitor) { visitor.Visit(m_Element); } + void Accept(ConstVisitor& visitor) const override { visitor.Visit(m_Element); } + void Accept(Visitor& visitor) override { visitor.Visit(m_Element); } }; } namespace json { -UnknownElement::UnknownElement() : m_pImp(new Imp_T(Null())) {} -UnknownElement::UnknownElement(UnknownElement&& unknown) : m_pImp(std::move(unknown.m_pImp)) {} +UnknownElement::UnknownElement() noexcept = default; UnknownElement::UnknownElement(int number) : m_pImp(new Imp_T(number)) {} UnknownElement::UnknownElement(const char *string) : m_pImp(new Imp_T(string)) {} +UnknownElement::UnknownElement(std::string_view string) : m_pImp(new Imp_T(String(string))) {} -UnknownElement::~UnknownElement() { } +UnknownElement::UnknownElement(UnknownElement&& unknown) noexcept = default; +UnknownElement& UnknownElement::operator=(UnknownElement&& unknown) noexcept = default; +UnknownElement::~UnknownElement() = default; #define DEFINE_UE_TYPE(Type) \ UnknownElement::UnknownElement(Type val) : m_pImp(new Imp_T(std::move(val))) { } \ @@ -76,37 +78,45 @@ DEFINE_UE_TYPE(Boolean) DEFINE_UE_TYPE(String) DEFINE_UE_TYPE(Null) -UnknownElement& UnknownElement::operator=(UnknownElement&& unknown) { - m_pImp = std::move(unknown.m_pImp); - return *this; -} - template ElementTypeT const& UnknownElement::CastTo() const { - CastVisitor castVisitor; - const_cast(this)->Accept(castVisitor); - if (!castVisitor.element) - throw Exception("Bad cast"); - return *castVisitor.element; + CastVisitor castVisitor; + const_cast(this)->Accept(castVisitor); + if (!castVisitor.element) + throw Exception("Bad cast"); + return *castVisitor.element; } template ElementTypeT& UnknownElement::CastTo() { - CastVisitor castVisitor; - Accept(castVisitor); - - // If this element is uninitialized, implicitly convert it to the desired type - if (castVisitor.is_null) { - *this = ElementTypeT(); - return *this; - } - - // Otherwise throw an exception - if (!castVisitor.element) - throw Exception("Bad cast"); - return *castVisitor.element; + CastVisitor castVisitor; + Accept(castVisitor); + + // If this element is uninitialized, implicitly convert it to the desired type + if (castVisitor.is_null) { + *this = ElementTypeT(); + return *this; + } + + // Otherwise throw an exception + if (!castVisitor.element) + throw Exception("Bad cast"); + return *castVisitor.element; } -void UnknownElement::Accept(ConstVisitor& visitor) const { m_pImp->Accept(visitor); } -void UnknownElement::Accept(Visitor& visitor) { m_pImp->Accept(visitor); } +void UnknownElement::Accept(ConstVisitor& visitor) const { + if (m_pImp) + m_pImp->Accept(visitor); + else + visitor.Visit(Null()); +} + +void UnknownElement::Accept(Visitor& visitor) { + if (m_pImp) + m_pImp->Accept(visitor); + else { + Null null; + visitor.Visit(null); + } +} } diff --git a/libaegisub/common/character_count.cpp b/libaegisub/common/character_count.cpp index 4563157a18..dcba76db93 100644 --- a/libaegisub/common/character_count.cpp +++ b/libaegisub/common/character_count.cpp @@ -18,74 +18,52 @@ #include "libaegisub/ass/dialogue_parser.h" #include "libaegisub/exception.h" +#include "libaegisub/unicode.h" #include #include #include -#include namespace { -struct utext_deleter { - void operator()(UText *ut) { if (ut) utext_close(ut); } -}; -using utext_ptr = std::unique_ptr; - -UChar32 ass_special_chars[] = {'n', 'N', 'h'}; - -icu::BreakIterator& get_break_iterator(const char *ptr, size_t len) { - static std::unique_ptr bi; - static std::once_flag token; - std::call_once(token, [&] { - UErrorCode status = U_ZERO_ERROR; - bi.reset(icu::BreakIterator::createCharacterInstance(icu::Locale::getDefault(), status)); - if (U_FAILURE(status)) throw agi::InternalError("Failed to create character iterator"); - }); - - UErrorCode err = U_ZERO_ERROR; - utext_ptr ut(utext_openUTF8(nullptr, ptr, len, &err)); - if (U_FAILURE(err)) throw agi::InternalError("Failed to open utext"); - - bi->setText(ut.get(), err); - if (U_FAILURE(err)) throw agi::InternalError("Failed to set break iterator text"); - - return *bi; -} +const std::basic_string_view ass_special_chars = U"nNh"; -template -size_t count_in_range(Iterator begin, Iterator end, int mask) { - if (begin == end) return 0; +size_t count_in_range(std::string_view str, int mask) { + if (str.empty()) return 0; - auto& character_bi = get_break_iterator(&*begin, end - begin); + thread_local agi::BreakIterator bi; + bi.set_text(str); size_t count = 0; - auto pos = character_bi.first(); - for (auto end = character_bi.next(); end != icu::BreakIterator::DONE; pos = end, end = character_bi.next()) { - if (!mask) + if (!mask) { + for (; !bi.done(); bi.next()) ++count; - else { - UChar32 c; - int i = 0; - U8_NEXT_UNSAFE(begin + pos, i, c); - if ((U_GET_GC_MASK(c) & mask) == 0) { - if (mask & U_GC_Z_MASK && pos != 0) { - UChar32 *result = std::find(std::begin(ass_special_chars), std::end(ass_special_chars), c); - if (result != std::end(ass_special_chars)) { - UChar32 c2; - i = 0; - U8_PREV_UNSAFE(begin + pos, i, c2); - if (c2 != (UChar32) '\\') - ++count; - else if (!(mask & U_GC_P_MASK)) - --count; - } - else - ++count; - } - else - ++count; - } + return count; + } + + UChar32 prev = 0; + for (; !bi.done(); bi.next()) { + // Getting the character category only requires the first codepoint of a character + UChar32 c; + int i = 0; + U8_NEXT(bi.current().data(), i, bi.current().size(), c); + UChar32 p = prev; + prev = c; + + if ((U_GET_GC_MASK(c) & mask) != 0) // if character is an ignored category + continue; + + // If previous character was a backslash and we're ignoring whitespace, + // check if this is an ass whitespace character (e.g. \h) + if (mask & U_GC_Z_MASK && p == '\\' && ass_special_chars.find(c) != ass_special_chars.npos) { + // If we're ignoring punctuation we didn't count the slash, but + // otherwise we need to uncount it + if (!(mask & U_GC_P_MASK)) + --count; + continue; } + + ++count; } return count; } @@ -101,33 +79,33 @@ int ignore_mask_to_icu_mask(int mask) { } namespace agi { -size_t CharacterCount(std::string::const_iterator begin, std::string::const_iterator end, int ignore) { +size_t CharacterCount(std::string_view str, int ignore) { int mask = ignore_mask_to_icu_mask(ignore); if ((ignore & agi::IGNORE_BLOCKS) == 0) - return count_in_range(begin, end, mask); + return count_in_range(str, mask); size_t characters = 0; - auto pos = begin; - do { - auto it = std::find(pos, end, '{'); - characters += count_in_range(pos, it, mask); - if (it == end) break; - - pos = std::find(pos, end, '}'); - if (pos == end) { - characters += count_in_range(it, pos, mask); - break; - } - } while (++pos != end); + while (!str.empty()) { + auto pos = str.find('{'); + if (pos == str.npos) break; + + // if there's no trailing }, the rest of the string counts as characters, + // including the leading { + auto end = str.find('}'); + if (end == str.npos) break; + + if (pos > 0) + characters += count_in_range(str.substr(0, pos), mask); + str.remove_prefix(end + 1); + } - return characters; -} + if (!str.empty()) + characters += count_in_range(str, mask); -size_t CharacterCount(std::string const& str, int mask) { - return CharacterCount(begin(str), end(str), mask); + return characters; } -size_t MaxLineLength(std::string const& text, int mask) { +size_t MaxLineLength(std::string_view text, int mask) { mask = ignore_mask_to_icu_mask(mask); auto tokens = agi::ass::TokenizeDialogueBody(text); agi::ass::MarkDrawings(text, tokens); @@ -147,7 +125,7 @@ size_t MaxLineLength(std::string const& text, int mask) { } } else if (token.type == agi::ass::DialogueTokenType::TEXT) - current_line_length += count_in_range(begin(text) + pos, begin(text) + pos + token.length, mask); + current_line_length += count_in_range(text.substr(pos, token.length), mask); pos += token.length; } @@ -155,15 +133,15 @@ size_t MaxLineLength(std::string const& text, int mask) { return std::max(max_line_length, current_line_length); } -size_t IndexOfCharacter(std::string const& str, size_t n) { +size_t IndexOfCharacter(std::string_view str, size_t n) { if (str.empty() || n == 0) return 0; - auto& bi = get_break_iterator(&str[0], str.size()); - - for (auto pos = bi.first(), end = bi.next(); ; --n, pos = end, end = bi.next()) { - if (end == icu::BreakIterator::DONE) - return str.size(); - if (n == 0) - return pos; - } + thread_local BreakIterator bi; + bi.set_text(str); + + for (; n > 0 && !bi.done(); --n) + bi.next(); + if (bi.done()) + return str.size(); + return bi.current().data() - str.data(); } } diff --git a/libaegisub/common/charset.cpp b/libaegisub/common/charset.cpp index fa25eea838..e00687b168 100644 --- a/libaegisub/common/charset.cpp +++ b/libaegisub/common/charset.cpp @@ -12,10 +12,6 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -/// @file charset.cpp -/// @brief Character set detection and manipulation utilities. -/// @ingroup libaegisub - #include "libaegisub/charset.h" #include "libaegisub/file_mapping.h" @@ -25,8 +21,8 @@ #include #endif -namespace agi { namespace charset { -std::string Detect(agi::fs::path const& file) { +namespace agi::charset { +std::string Detect(std::filesystem::path const& file) { agi::read_file_mapping fp(file); // First check for known magic bytes which identify the file type @@ -86,4 +82,4 @@ std::string Detect(agi::fs::path const& file) { return "utf-8"; #endif } -} } +} diff --git a/libaegisub/common/charset_6937.cpp b/libaegisub/common/charset_6937.cpp index c0e446a14e..7c4b9c2332 100644 --- a/libaegisub/common/charset_6937.cpp +++ b/libaegisub/common/charset_6937.cpp @@ -152,11 +152,9 @@ const extended_range iso6937_extended_codepoints[] = { { 0x266A, 0xD5 } }; -#define countof(array) (sizeof(array) / sizeof((array)[0])) - /// Get the ISO-6937-2 value for the given unicode codepoint or 0 if it cannot be mapped int get_iso6937(int codepoint) { - if (static_cast(codepoint) < countof(iso6937_codepoints)) + if (static_cast(codepoint) < std::size(iso6937_codepoints)) return iso6937_codepoints[codepoint]; auto ext = boost::lower_bound(iso6937_extended_codepoints, codepoint); @@ -167,7 +165,7 @@ int get_iso6937(int codepoint) { } // namespace { -namespace agi { namespace charset { +namespace agi::charset { #ifdef _LIBICONV_VERSION #define INTERNAL_CHARSET "UCS-4-INTERNAL" @@ -176,7 +174,7 @@ namespace agi { namespace charset { #endif Converter6937::Converter6937(bool subst, const char *src) -: to_ucs4(new IconvWrapper(src, INTERNAL_CHARSET)) +: to_ucs4(Converter::create(true, src, INTERNAL_CHARSET)) , subst(subst) { } @@ -243,4 +241,4 @@ size_t Converter6937::Convert(const char **inbuf, size_t *inbytesleft, char **ou return bytes_written; } -} } // namespace agi::charset +} // namespace agi::charset diff --git a/libaegisub/common/charset_6937.h b/libaegisub/common/charset_6937.h index b95113bf08..ea40679f81 100644 --- a/libaegisub/common/charset_6937.h +++ b/libaegisub/common/charset_6937.h @@ -19,7 +19,7 @@ #include #include -namespace agi { namespace charset { +namespace agi::charset { /// @brief A charset converter for ISO-6937-2 /// @@ -27,7 +27,7 @@ namespace agi { namespace charset { /// it's not used by anything but old subtitle formats class Converter6937 final : public Converter { /// Converter to UCS-4 so that we only have to deal with unicode codepoints - std::unique_ptr to_ucs4; + std::unique_ptr to_ucs4; /// Should unsupported characters be replaced with '?' const bool subst; @@ -42,4 +42,4 @@ class Converter6937 final : public Converter { size_t Convert(const char** inbuf, size_t* inbytesleft, char** outbuf, size_t* outbytesleft); }; -} } +} diff --git a/libaegisub/common/charset_conv.cpp b/libaegisub/common/charset_conv.cpp index 47644eb1c9..4652049a51 100644 --- a/libaegisub/common/charset_conv.cpp +++ b/libaegisub/common/charset_conv.cpp @@ -12,21 +12,18 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -/// @file charset_conv.cpp -/// @brief Wrapper for libiconv to present a more C++-friendly API -/// @ingroup libaegisub +#include -#include -#include +#include +#include "charset_6937.h" -#include +#include #include - -#include +#include +#include +#include #include -#include "charset_6937.h" - // Check if we can use advanced fallback capabilities added in GNU's iconv // implementation #if !defined(_LIBICONV_VERSION) || _LIBICONV_VERSION < 0x010A || defined(LIBICONV_PLUG) @@ -42,11 +39,11 @@ static const iconv_t iconv_invalid = (iconv_t)-1; static const size_t iconv_failed = (size_t)-1; +using namespace std::string_view_literals; + namespace { using namespace agi::charset; - Converter *get_converter(bool subst, const char *src, const char *dst); - /// @brief Map a user-friendly encoding name to the real encoding name const char *get_real_encoding_name(const char *name) { struct pair { const char *pretty; const char *real; }; @@ -69,60 +66,20 @@ namespace { }); if (enc != std::end(pretty_names) && strcmp(enc->pretty, name) == 0) - return enc->real; + name = enc->real; + // UTF-16 and UTF-32 encode a BOM, which we don't want + if (boost::iequals(name, "utf-16")) + name = "utf-16be"; + if (boost::iequals(name, "utf-32")) + name = "utf-32be"; return name; } - size_t get_bom_size(Iconv& cd) { - // Most (but not all) iconv implementations automatically insert a BOM - // at the beginning of text converted to UTF-8, UTF-16 and UTF-32, but - // we usually don't want this, as some of the wxString using code - // assumes there is no BOM (as the exact encoding is known externally) - // As such, when doing conversions we will strip the BOM if it exists, - // then manually add it when writing files - - char buff[8]; - const char* src = ""; - char *dst = buff; - size_t srcLen = 1; - size_t dstLen = 8; - - size_t res = cd(&src, &srcLen, &dst, &dstLen); - assert(res != iconv_failed); - assert(srcLen == 0); - - size_t size = 0; - for (src = buff; src < dst; ++src) { - if (*src) ++size; - } - if (size) { - // If there is a BOM, it will always be at least as big as the NUL - size = std::max(size, (8 - dstLen) / 2); - } - return size; - } - - void eat_bom(Iconv& cd, size_t bomSize, const char** inbuf, size_t* inbytesleft, char** outbuf, size_t* outbytesleft) { - // If this encoding has a forced BOM (i.e. it's UTF-16 or UTF-32 without - // a specified byte order), skip over it - if (bomSize > 0 && inbytesleft && *inbytesleft) { - // libiconv marks the bom as written after writing the first - // character after the bom rather than when it writes the bom, so - // convert at least one extra character - char bom[8]; - char *dst = bom; - size_t dstSize = std::min((size_t)8, bomSize + *outbytesleft); - const char *src = *inbuf; - size_t srcSize = *inbytesleft; - cd(&src, &srcSize, &dst, &dstSize); - } - } - // Calculate the size of NUL in the given character set size_t nul_size(const char *encoding) { // We need a character set to convert from with a known encoding of NUL // UTF-8 seems like the obvious choice - std::unique_ptr cd(get_converter(false, "UTF-8", encoding)); + auto cd = Converter::create(false, "UTF-8", encoding); char dbuff[4]; char sbuff[] = ""; @@ -140,22 +97,16 @@ namespace { #ifdef ICONV_POSIX class ConverterImpl final : public Converter { - size_t bomSize; Iconv cd; public: // subst is not used here because POSIX doesn't let you disable substitution ConverterImpl(bool, const char* sourceEncoding, const char* destEncoding) { const char *dstEnc = get_real_encoding_name(destEncoding); - cd = Iconv("utf-8", dstEnc); - - bomSize = get_bom_size(cd); cd = Iconv(get_real_encoding_name(sourceEncoding), dstEnc); } size_t Convert(const char** inbuf, size_t* inbytesleft, char** outbuf, size_t* outbytesleft) { - eat_bom(cd, bomSize, inbuf, inbytesleft, outbuf, outbytesleft); - size_t res = cd(inbuf, inbytesleft, outbuf, outbytesleft); // This loop never does anything useful with a POSIX-compliant iconv @@ -173,7 +124,6 @@ namespace { #else class ConverterImpl final : public iconv_fallbacks, public Converter { - size_t bomSize; char invalidRep[8]; size_t invalidRepSize; Iconv cd; @@ -189,7 +139,7 @@ namespace { if (code == 0xFEFF) return; if (code == 0x5C) callback("\\", 1, callback_arg); else { - ConverterImpl *self = static_cast(convPtr); + auto *self = static_cast(convPtr); callback(self->invalidRep, self->invalidRepSize, callback_arg); } } @@ -200,8 +150,6 @@ namespace { const char *dstEnc = get_real_encoding_name(destEncoding); cd = Iconv("utf-8", dstEnc); - bomSize = get_bom_size(cd); - // Get fallback character const char sbuff[] = "?"; const char *src = sbuff; @@ -231,7 +179,6 @@ namespace { } size_t Convert(const char** inbuf, size_t* inbytesleft, char** outbuf, size_t* outbytesleft) override { - eat_bom(cd, bomSize, inbuf, inbytesleft, outbuf, outbytesleft); size_t res = cd(inbuf, inbytesleft, outbuf, outbytesleft); if (res == iconv_failed && errno == E2BIG && *outbytesleft == 0) { @@ -258,27 +205,16 @@ namespace { } }; #endif - - Converter *get_converter(bool subst, const char *src, const char *dst) { - try { - return new ConverterImpl(subst, src, dst); - } - catch (UnsupportedConversion const&) { - if (strcmp(dst, "ISO-6937-2")) - throw; - return new Converter6937(subst, src); - } - } } // namespace { -namespace agi { namespace charset { +namespace agi::charset { Iconv::Iconv() : cd(iconv_invalid) { } Iconv::Iconv(const char *source, const char *dest) : cd(iconv_open(dest, source)) { if (cd == iconv_invalid) - throw UnsupportedConversion(std::string("Cannot convert from ") + source + " to " + dest); + throw UnsupportedConversion(agi::Str("Cannot convert from ", source, " to ", dest)); } Iconv::~Iconv() { @@ -289,8 +225,19 @@ size_t Iconv::operator()(const char **inbuf, size_t *inbytesleft, char **outbuf, return iconv(cd, ICONV_CONST_CAST(inbuf), inbytesleft, outbuf, outbytesleft); } -IconvWrapper::IconvWrapper(const char* sourceEncoding, const char* destEncoding, bool enableSubst) -: conv(get_converter(enableSubst, sourceEncoding, destEncoding)) +std::unique_ptr Converter::create(bool subst, const char *src, const char *dst) { + try { + return std::make_unique(subst, src, dst); + } + catch (UnsupportedConversion const&) { + if (dst != "ISO-6937-2"sv) + throw; + return std::make_unique(subst, src); + } +} + +IconvWrapper::IconvWrapper(const char *sourceEncoding, const char *destEncoding, bool enableSubst) +: conv(Converter::create(enableSubst, sourceEncoding, destEncoding)) { // These need to be set only after we verify that the source and dest // charsets are valid @@ -298,16 +245,18 @@ IconvWrapper::IconvWrapper(const char* sourceEncoding, const char* destEncoding, fromNulLen = nul_size(sourceEncoding); } -IconvWrapper::~IconvWrapper() { } +IconvWrapper::~IconvWrapper() = default; -std::string IconvWrapper::Convert(const char *source, size_t len) { +std::string IconvWrapper::Convert(std::string_view source) { std::string dest; - Convert(source, len, dest); + Convert(source, dest); return dest; } -void IconvWrapper::Convert(const char *src, size_t srcLen, std::string &dest) { +void IconvWrapper::Convert(std::string_view src_view, std::string &dest) { char buff[512]; + auto src = src_view.data(); + auto srcLen = src_view.size(); size_t res; do { @@ -332,12 +281,14 @@ void IconvWrapper::Convert(const char *src, size_t srcLen, std::string &dest) { } } -size_t IconvWrapper::Convert(const char* source, size_t sourceSize, char *dest, size_t destSize) { - if (sourceSize == (size_t)-1) - sourceSize = SrcStrLen(source); +size_t IconvWrapper::Convert(std::string_view source, std::span dest) { + auto src = source.data(); + auto srcLen = source.size(); + auto dst = dest.data(); + auto dstLen = dest.size(); - size_t res = conv->Convert(&source, &sourceSize, &dest, &destSize); - if (res == 0) res = conv->Convert(nullptr, nullptr, &dest, &destSize); + size_t res = conv->Convert(&src, &srcLen, &dst, &dstLen); + if (res == 0) res = conv->Convert(nullptr, nullptr, &dst, &dstLen); if (res == iconv_failed) { switch (errno) { @@ -354,67 +305,7 @@ size_t IconvWrapper::Convert(const char* source, size_t sourceSize, char *dest, throw ConversionFailure("An unknown conversion failure occurred"); } } - return res; -} - -size_t IconvWrapper::Convert(const char** source, size_t* sourceSize, char** dest, size_t* destSize) { - return conv->Convert(source, sourceSize, dest, destSize); -} - -size_t IconvWrapper::RequiredBufferSize(std::string const& str) { - return RequiredBufferSize(str.data(), str.size()); -} - -size_t IconvWrapper::RequiredBufferSize(const char* src, size_t srcLen) { - char buff[4096]; - size_t charsWritten = 0; - size_t res; - - do { - char* dst = buff; - size_t dstSize = sizeof(buff); - res = conv->Convert(&src, &srcLen, &dst, &dstSize); - conv->Convert(nullptr, nullptr, &dst, &dstSize); - - charsWritten += dst - buff; - } while (res == iconv_failed && errno == E2BIG); - - if (res == iconv_failed) { - switch (errno) { - case EINVAL: - case EILSEQ: - throw BadInput( - "One or more characters in the input string were not valid " - "characters in the given input encoding"); - default: - throw ConversionFailure("An unknown conversion failure occurred"); - } - } - return charsWritten; -} - -static size_t mbstrlen(const char* str, size_t nulLen) { - const char *ptr; - switch (nulLen) { - case 1: - return strlen(str); - case 2: - for (ptr = str; *reinterpret_cast(ptr) != 0; ptr += 2) ; - return ptr - str; - case 4: - for (ptr = str; *reinterpret_cast(ptr) != 0; ptr += 4) ; - return ptr - str; - default: - return (size_t)-1; - } -} - -size_t IconvWrapper::SrcStrLen(const char* str) { - return mbstrlen(str, fromNulLen); - -} -size_t IconvWrapper::DstStrLen(const char* str) { - return mbstrlen(str, toNulLen); + return dest.size() - dstLen; } bool IsConversionSupported(const char *src, const char *dst) { @@ -424,5 +315,4 @@ bool IsConversionSupported(const char *src, const char *dst) { return supported; } - } -} +} // namespace agi::charset diff --git a/libaegisub/common/color.cpp b/libaegisub/common/color.cpp index 7381e92ffc..e46a6cbdf1 100644 --- a/libaegisub/common/color.cpp +++ b/libaegisub/common/color.cpp @@ -24,7 +24,7 @@ Color::Color(unsigned char r, unsigned char g, unsigned char b, unsigned char a) : r(r), g(g), b(b), a(a) { } -Color::Color(std::string const& str) { +Color::Color(std::string_view str) { parser::parse(*this, str); } @@ -50,12 +50,4 @@ std::string Color::GetRgbFormatted() const { return agi::format("rgb(%d, %d, %d)", r, g, b); } -bool Color::operator==(Color const& col) const { - return r == col.r && g == col.g && b == col.b && a == col.a; -} - -bool Color::operator!=(Color const& col) const { - return !(*this == col); -} - } diff --git a/libaegisub/common/dispatch.cpp b/libaegisub/common/dispatch.cpp index 434195836e..424d60e555 100644 --- a/libaegisub/common/dispatch.cpp +++ b/libaegisub/common/dispatch.cpp @@ -31,13 +31,13 @@ namespace { std::atomic threads_running; class MainQueue final : public agi::dispatch::Queue { - void DoInvoke(agi::dispatch::Thunk thunk) override { + void DoInvoke(agi::dispatch::Thunk&& thunk) override { invoke_main(thunk); } }; class BackgroundQueue final : public agi::dispatch::Queue { - void DoInvoke(agi::dispatch::Thunk thunk) override { + void DoInvoke(agi::dispatch::Thunk&& thunk) override { service->post(thunk); } }; @@ -45,7 +45,7 @@ namespace { class SerialQueue final : public agi::dispatch::Queue { boost::asio::io_service::strand strand; - void DoInvoke(agi::dispatch::Thunk thunk) override { + void DoInvoke(agi::dispatch::Thunk&& thunk) override { strand.post(thunk); } public: @@ -72,9 +72,9 @@ namespace { }; } -namespace agi { namespace dispatch { +namespace agi::dispatch { -void Init(std::function invoke_main) { +void Init(std::function&& invoke_main) { static IOServiceThreadPool thread_pool; ::service = &thread_pool.io_service; ::invoke_main = invoke_main; @@ -90,7 +90,7 @@ void Init(std::function invoke_main) { } } -void Queue::Async(Thunk thunk) { +void Queue::Async(Thunk&& thunk) { DoInvoke([=] { try { thunk(); @@ -102,7 +102,7 @@ void Queue::Async(Thunk thunk) { }); } -void Queue::Sync(Thunk thunk) { +void Queue::Sync(Thunk&& thunk) { std::mutex m; std::condition_variable cv; std::unique_lock l(m); @@ -137,4 +137,4 @@ std::unique_ptr Create() { return std::unique_ptr(new SerialQueue); } -} } +} diff --git a/libaegisub/common/file_mapping.cpp b/libaegisub/common/file_mapping.cpp index 5102694963..8f715fd112 100644 --- a/libaegisub/common/file_mapping.cpp +++ b/libaegisub/common/file_mapping.cpp @@ -17,10 +17,8 @@ #include "libaegisub/file_mapping.h" #include "libaegisub/fs.h" -#include "libaegisub/make_unique.h" #include "libaegisub/util.h" -#include #include #include @@ -62,7 +60,7 @@ char *map(int64_t s_offset, uint64_t length, boost::interprocess::mode_t mode, throw std::bad_alloc(); try { - region = agi::make_unique(file, mode, mapping_start, static_cast(length)); + region = std::make_unique(file, mode, mapping_start, static_cast(length)); } catch (interprocess_exception const&) { throw agi::fs::FileSystemUnknownError("Failed mapping a view of the file"); @@ -74,7 +72,7 @@ char *map(int64_t s_offset, uint64_t length, boost::interprocess::mode_t mode, } namespace agi { -file_mapping::file_mapping(fs::path const& filename, bool temporary) +file_mapping::file_mapping(std::filesystem::path const& filename, bool temporary) #ifdef _WIN32 : handle(CreateFileW(filename.wstring().c_str(), temporary ? read_write : read_only, @@ -118,7 +116,7 @@ file_mapping::~file_mapping() { } } -read_file_mapping::read_file_mapping(fs::path const& filename) +read_file_mapping::read_file_mapping(std::filesystem::path const& filename) : file(filename, false) { offset_t size = 0; @@ -126,7 +124,7 @@ read_file_mapping::read_file_mapping(fs::path const& filename) file_size = static_cast(size); } -read_file_mapping::~read_file_mapping() { } +read_file_mapping::~read_file_mapping() = default; const char *read_file_mapping::read() { return read(0, size()); @@ -136,7 +134,7 @@ const char *read_file_mapping::read(int64_t offset, uint64_t length) { return map(offset, length, read_only, file_size, file, region, mapping_start); } -temp_file_mapping::temp_file_mapping(fs::path const& filename, uint64_t size) +temp_file_mapping::temp_file_mapping(std::filesystem::path const& filename, uint64_t size) : file(filename, true) , file_size(size) { @@ -160,7 +158,7 @@ temp_file_mapping::temp_file_mapping(fs::path const& filename, uint64_t size) #endif } -temp_file_mapping::~temp_file_mapping() { } +temp_file_mapping::~temp_file_mapping() = default; const char *temp_file_mapping::read(int64_t offset, uint64_t length) { return map(offset, length, read_only, file_size, file, read_region, read_mapping_start); diff --git a/libaegisub/common/format.cpp b/libaegisub/common/format.cpp index 829baa782a..8c58e498d4 100644 --- a/libaegisub/common/format.cpp +++ b/libaegisub/common/format.cpp @@ -17,9 +17,8 @@ #include #include -#include -#include +#include #ifdef _MSC_VER #define WCHAR_T_ENC "utf-16le" @@ -34,21 +33,21 @@ template class boost::interprocess::basic_vectorbuf; namespace { template -int actual_len(int max_len, const Char *value) { +size_t actual_len(size_t max_len, const Char *value) { int len = 0; while (value[len] && (max_len <= 0 || len < max_len)) ++len; return len; } template -int actual_len(int max_len, std::basic_string value) { +size_t actual_len(size_t max_len, std::basic_string value) { if (max_len > 0 && static_cast(max_len) < value.size()) return max_len; return value.size(); } template -void do_write_str(std::basic_ostream& out, const Char *str, int len) { +void do_write_str(std::basic_ostream& out, const Char *str, size_t len) { out.write(str, len); } @@ -204,12 +203,12 @@ struct format_parser { namespace agi { template -void writer::write(std::basic_ostream& out, int max_len, const Char *value) { +void writer::write(std::basic_ostream& out, size_t max_len, const Char *value) { do_write_str(out, value, actual_len(max_len, value)); } template -void writer>::write(std::basic_ostream& out, int max_len, std::basic_string const& value) { +void writer>::write(std::basic_ostream& out, size_t max_len, std::basic_string const& value) { do_write_str(out, value.data(), actual_len(max_len, value)); } diff --git a/libaegisub/common/fs.cpp b/libaegisub/common/fs.cpp index 6ce45a08ee..a4a59a05db 100644 --- a/libaegisub/common/fs.cpp +++ b/libaegisub/common/fs.cpp @@ -20,38 +20,42 @@ #include "libaegisub/log.h" #include -#define BOOST_NO_SCOPED_ENUMS -#include -#undef BOOST_NO_SCOPED_ENUMS +#include -namespace bfs = boost::filesystem; -namespace ec = boost::system::errc; +namespace bfs = std::filesystem; -// boost::filesystem functions throw a single exception type for all +namespace agi::fs { +namespace { +void check_error(std::error_code ec, const char *exp, bfs::path const& src_path, bfs::path const& dst_path) { + if (ec == std::error_code{}) return; + using enum std::errc; + switch (ec.value()) { + case int(no_such_file_or_directory): throw FileNotFound(src_path); + case int(is_a_directory): throw NotAFile(src_path); + case int(not_a_directory): throw NotADirectory(src_path); + case int(no_space_on_device): throw DriveFull(dst_path); + case int(permission_denied): + if (!src_path.empty()) + acs::CheckFileRead(src_path); + if (!dst_path.empty()) + acs::CheckFileWrite(dst_path); + throw AccessDenied(src_path); + default: + LOG_D("filesystem") << "Unknown error when calling '" << exp << "': " << ec << ": " << ec.message(); + throw FileSystemUnknownError(ec.message()); + } +} + +// std::filesystem functions throw a single exception type for all // errors, which isn't really what we want, so do some crazy wrapper // shit to map error codes to more useful exceptions. #define CHECKED_CALL(exp, src_path, dst_path) \ - boost::system::error_code ec; \ + std::error_code ec; \ exp; \ - switch (ec.value()) {\ - case ec::success: break; \ - case ec::no_such_file_or_directory: throw FileNotFound(src_path); \ - case ec::is_a_directory: throw NotAFile(src_path); \ - case ec::not_a_directory: throw NotADirectory(src_path); \ - case ec::no_space_on_device: throw DriveFull(dst_path); \ - case ec::permission_denied: \ - if (!src_path.empty()) \ - acs::CheckFileRead(src_path); \ - if (!dst_path.empty()) \ - acs::CheckFileWrite(dst_path); \ - throw AccessDenied(src_path); \ - default: \ - LOG_D("filesystem") << "Unknown error when calling '" << #exp << "': " << ec << ": " << ec.message(); \ - throw FileSystemUnknownError(ec.message()); \ - } + check_error(ec, #exp, src_path, dst_path); #define CHECKED_CALL_RETURN(exp, src_path) \ - CHECKED_CALL(auto ret = exp, src_path, agi::fs::path()); \ + CHECKED_CALL(auto ret = exp, src_path, std::filesystem::path()); \ return ret #define WRAP_BFS(bfs_name, agi_name) \ @@ -61,18 +65,16 @@ namespace ec = boost::system::errc; #define WRAP_BFS_IGNORE_ERROR(bfs_name, agi_name) \ auto agi_name(path const& p) -> decltype(bfs::bfs_name(p)) { \ - boost::system::error_code ec; \ + std::error_code ec; \ return bfs::bfs_name(p, ec); \ } // sasuga windows.h #undef CreateDirectory -namespace agi { namespace fs { -namespace { WRAP_BFS(file_size, SizeImpl) WRAP_BFS(space, Space) -} +} // anonymous namespace WRAP_BFS_IGNORE_ERROR(exists, Exists) WRAP_BFS_IGNORE_ERROR(is_regular_file, FileExists) @@ -102,4 +104,4 @@ namespace { if (filename[filename.size() - ext.size() - 1] != '.') return false; return boost::iends_with(filename, ext); } -} } +} diff --git a/libaegisub/common/hotkey.cpp b/libaegisub/common/hotkey.cpp index a2cebeef93..48f9f776fb 100644 --- a/libaegisub/common/hotkey.cpp +++ b/libaegisub/common/hotkey.cpp @@ -23,30 +23,31 @@ #include #include #include +#include -namespace agi { namespace hotkey { +namespace agi::hotkey { namespace { struct combo_cmp { bool operator()(const Combo *a, const Combo *b) { return a->Str() < b->Str(); } - bool operator()(const Combo *a, std::string const& b) { + bool operator()(const Combo *a, std::string_view b) { return a->Str() < b; } - bool operator()(std::string const& a, const Combo *b) { + bool operator()(std::string_view a, const Combo *b) { return a < b->Str(); } }; struct hotkey_visitor : json::ConstVisitor { - std::string const& context; - std::string const& command; + std::string_view context; + std::string_view command; Hotkey::HotkeyMap& map; bool needs_backup = false; - hotkey_visitor(std::string const& context, std::string const& command, Hotkey::HotkeyMap& map) + hotkey_visitor(std::string_view context, std::string_view command, Hotkey::HotkeyMap& map) : context(context), command(command), map(map) { } void Visit(std::string const& string) override { @@ -85,7 +86,7 @@ struct hotkey_visitor : json::ConstVisitor { }; } -Hotkey::Hotkey(fs::path const& file, std::pair default_config) +Hotkey::Hotkey(fs::path const& file, std::string_view default_config) : config_file(file) { LOG_D("hotkey/init") << "Generating hotkeys."; @@ -96,7 +97,7 @@ Hotkey::Hotkey(fs::path const& file, std::pair default_con UpdateStrMap(); } -void Hotkey::BuildHotkey(std::string const& context, json::Object const& hotkeys) { +void Hotkey::BuildHotkey(std::string_view context, json::Object const& hotkeys) { for (auto const& command : hotkeys) { json::Array const& command_hotkeys = command.second; @@ -107,50 +108,48 @@ void Hotkey::BuildHotkey(std::string const& context, json::Object const& hotkeys } } -std::string Hotkey::Scan(std::string const& context, std::string const& str, bool always) const { - const std::string *local = nullptr, *dfault = nullptr; +std::string_view Hotkey::Scan(std::string_view context, std::string_view str, bool always) const { + std::string_view local, dfault; - std::vector::const_iterator index, end; - for (std::tie(index, end) = boost::equal_range(str_map, str, combo_cmp()); index != end; ++index) { - std::string const& ctext = (*index)->Context(); + for (auto [index, end] = boost::equal_range(str_map, str, combo_cmp()); index != end; ++index) { + std::string_view ctext = (*index)->Context(); if (always && ctext == "Always") { LOG_D("agi/hotkey/found") << "Found: " << str << " Context (req/found): " << context << "/Always Command: " << (*index)->CmdName(); return (*index)->CmdName(); } if (ctext == "Default") - dfault = &(*index)->CmdName(); + dfault = (*index)->CmdName(); else if (ctext == context) - local = &(*index)->CmdName(); + local = (*index)->CmdName(); } - if (local) { - LOG_D("agi/hotkey/found") << "Found: " << str << " Context: " << context << " Command: " << *local; - return *local; + if (local.data()) { + LOG_D("agi/hotkey/found") << "Found: " << str << " Context: " << context << " Command: " << local; + return local; } - if (dfault) { - LOG_D("agi/hotkey/found") << "Found: " << str << " Context (req/found): " << context << "/Default Command: " << *dfault; - return *dfault; + if (dfault.data()) { + LOG_D("agi/hotkey/found") << "Found: " << str << " Context (req/found): " << context << "/Default Command: " << dfault; + return dfault; } return ""; } -bool Hotkey::HasHotkey(std::string const& context, std::string const& str) const { - std::vector::const_iterator index, end; - for (std::tie(index, end) = boost::equal_range(str_map, str, combo_cmp()); index != end; ++index) { +bool Hotkey::HasHotkey(std::string_view context, std::string_view str) const { + for (auto [index, end] = boost::equal_range(str_map, str, combo_cmp()); index != end; ++index) { if (context == (*index)->Context()) return true; } return false; } -std::vector Hotkey::GetHotkeys(std::string const& context, std::string const& command) const { +std::vector Hotkey::GetHotkeys(std::string_view context, std::string_view command) const { std::vector ret; HotkeyMap::const_iterator it, end; - for (std::tie(it, end) = cmd_map.equal_range(command); it != end; ++it) { - std::string const& ctext = it->second.Context(); + for (auto [it, end] = cmd_map.equal_range(command); it != end; ++it) { + std::string_view ctext = it->second.Context(); if (ctext == "Always" || ctext == "Default" || ctext == context) ret.emplace_back(it->second.Str()); } @@ -161,11 +160,10 @@ std::vector Hotkey::GetHotkeys(std::string const& context, std::str return ret; } -std::string Hotkey::GetHotkey(std::string const& context, std::string const& command) const { - std::string ret; - HotkeyMap::const_iterator it, end; - for (std::tie(it, end) = cmd_map.equal_range(command); it != end; ++it) { - std::string const& ctext = it->second.Context(); +std::string_view Hotkey::GetHotkey(std::string_view context, std::string_view command) const { + std::string_view ret; + for (auto [it, end] = cmd_map.equal_range(command); it != end; ++it) { + std::string_view ctext = it->second.Context(); if (ctext == context) return it->second.Str(); if (ctext == "Default") ret = it->second.Str(); @@ -178,13 +176,13 @@ std::string Hotkey::GetHotkey(std::string const& context, std::string const& com void Hotkey::Flush() { json::Object root; - for (auto const& combo : str_map) { - auto const& keys = combo->Str(); - if (keys.empty()) continue; + auto get = [](json::Object& obj, std::string_view key) -> json::UnknownElement& { + return obj.emplace(key, json::UnknownElement()).first->second; + }; - json::Object& context = root[combo->Context()]; - json::Array& combo_array = context[combo->CmdName()]; - combo_array.push_back(keys); + for (auto const& combo : str_map) { + json::Object& context = get(root, combo->Context()); + static_cast(get(context, combo->CmdName())).push_back(combo->Str()); } if (backup_config_file && fs::FileExists(config_file) && !fs::FileExists(config_file.string() + ".3_1")) @@ -210,4 +208,4 @@ void Hotkey::SetHotkeyMap(HotkeyMap new_map) { HotkeysChanged(); } -} } +} diff --git a/libaegisub/common/io.cpp b/libaegisub/common/io.cpp index e133a71abb..55fe42575f 100644 --- a/libaegisub/common/io.cpp +++ b/libaegisub/common/io.cpp @@ -12,28 +12,22 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -/// @file io.cpp -/// @brief Windows IO methods. -/// @ingroup libaegisub - #include "libaegisub/io.h" #include #include "libaegisub/fs.h" #include "libaegisub/log.h" -#include "libaegisub/make_unique.h" #include "libaegisub/util.h" -#include -#include +#include -namespace agi { - namespace io { +namespace agi::io { +using namespace std::filesystem; -std::unique_ptr Open(fs::path const& file, bool binary) { +std::unique_ptr Open(path const& file, bool binary) { LOG_D("agi/io/open/file") << file; - auto stream = agi::make_unique(file, (binary ? std::ios::binary : std::ios::in)); + auto stream = std::make_unique(file, (binary ? std::ios::binary : std::ios::in)); if (stream->fail()) { acs::CheckFileRead(file); throw IOFatal("Unknown fatal error occurred opening " + file.string()); @@ -42,13 +36,13 @@ std::unique_ptr Open(fs::path const& file, bool binary) { return std::unique_ptr(stream.release()); } -Save::Save(fs::path const& file, bool binary) +Save::Save(path const& file, bool binary) : file_name(file) -, tmp_name(unique_path(file.parent_path()/(file.stem().string() + "_tmp_%%%%" + file.extension().string()))) +, tmp_name(file.parent_path()/(file.stem().string() + ".tmp" + file.extension().string())) { LOG_D("agi/io/save/file") << file; - fp = agi::make_unique(tmp_name, binary ? std::ios::binary : std::ios::out); + fp = std::make_unique(tmp_name, binary ? std::ios::binary : std::ios::out); if (!fp->good()) { acs::CheckDirWrite(file.parent_path()); acs::CheckFileWrite(file); @@ -72,5 +66,4 @@ Save::~Save() noexcept(false) { } } - } // namespace io -} // namespace agi +} // namespace agi::io diff --git a/libaegisub/common/json.cpp b/libaegisub/common/json.cpp index 493ca2f5d8..cb65d20e6d 100644 --- a/libaegisub/common/json.cpp +++ b/libaegisub/common/json.cpp @@ -12,10 +12,6 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -/// @file json.cpp -/// @brief Parse JSON files and return json::UnknownElement -/// @ingroup libaegisub io - #include "libaegisub/json.h" #include "libaegisub/cajun/reader.h" @@ -25,7 +21,7 @@ #include -namespace agi { namespace json_util { +namespace agi::json_util { json::UnknownElement parse(std::istream &stream) { try { @@ -41,7 +37,7 @@ json::UnknownElement parse(std::istream &stream) { } } -json::UnknownElement file(agi::fs::path const& file, std::pair default_config) { +json::UnknownElement file(std::filesystem::path const& file, std::string_view default_config) { try { if (fs::FileExists(file)) return parse(*io::Open(file)); @@ -55,8 +51,8 @@ json::UnknownElement file(agi::fs::path const& file, std::pair +// Copyright (c) 2013, Thomas Goyne // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -578,21 +578,21 @@ agi::kana_pair romaji_to_kana[] = { {"\xE3\x83\x85", "zu"}, // ヅ }; -bool cmp_kana(agi::kana_pair const& kp, std::string const& kana) { - return strcmp(kp.kana, kana.c_str()) < 0; +bool cmp_kana(agi::kana_pair const& kp, std::string_view kana) { + return kp.kana < kana; } struct cmp_romaji { - bool operator()(agi::kana_pair const& kp, std::string const& romaji) const { - return strcmp(kp.romaji, romaji.c_str()) < 0; + bool operator()(agi::kana_pair const& kp, std::string_view romaji) const { + return kp.romaji < romaji; } - bool operator()(std::string const& romaji, agi::kana_pair const& kp) const { - return strcmp(kp.romaji, romaji.c_str()) > 0; + bool operator()(std::string_view romaji, agi::kana_pair const& kp) const { + return romaji < kp.romaji; } #ifdef _MSC_VER // debug iterator stuff needs this overload bool operator()(agi::kana_pair const& a, agi::kana_pair const& b) const { - return strcmp(a.romaji, b.romaji) < 0; + return a.romaji < b.romaji; } #endif }; @@ -600,18 +600,18 @@ struct cmp_romaji { } namespace agi { -std::vector kana_to_romaji(std::string const& kana) { - std::vector ret; +std::vector kana_to_romaji(std::string_view kana) { + std::vector ret; for (auto pair = boost::lower_bound(::kana_to_romaji, kana, cmp_kana); - pair != std::end(::kana_to_romaji) && !strcmp(pair->kana, kana.c_str()); - ++pair) + pair != std::end(::kana_to_romaji) && pair->kana == kana; + ++pair) ret.push_back(pair->romaji); return ret; } -boost::iterator_range romaji_to_kana(std::string const& romaji) { +boost::iterator_range romaji_to_kana(std::string_view romaji) { for (size_t len = std::min(3, romaji.size()); len > 0; --len) { - auto pair = boost::equal_range(::romaji_to_kana, romaji.substr(0, len).c_str(), cmp_romaji()); + auto pair = boost::equal_range(::romaji_to_kana, romaji.substr(0, len), cmp_romaji()); if (pair.first != pair.second) return boost::make_iterator_range(pair.first, pair.second); } diff --git a/libaegisub/common/karaoke_matcher.cpp b/libaegisub/common/karaoke_matcher.cpp index 562b0273fe..8d8477b4a7 100644 --- a/libaegisub/common/karaoke_matcher.cpp +++ b/libaegisub/common/karaoke_matcher.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2013, Thomas Goyne +// Copyright (c) 2022, Thomas Goyne // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -16,14 +16,16 @@ #include "libaegisub/karaoke_matcher.h" +#include "libaegisub/ass/karaoke.h" +#include "libaegisub/exception.h" #include "libaegisub/kana_table.h" +#include "libaegisub/scoped_ptr.h" #include "libaegisub/util.h" #include -#include -#include -#include #include +#include +#include #include #include @@ -38,60 +40,58 @@ bool is_whitespace(int32_t c) { return !!u_isUWhiteSpace(c); } -bool is_whitespace(std::string const& str) { +bool is_whitespace(std::string_view str) { size_t i = 0; - while (auto c = next_codepoint(str.c_str(), &i)) { - if (!u_isUWhiteSpace(c)) - return false; + while (i < str.size()) { + UChar32 c; + U8_NEXT(str.data(), i, str.size(), c); + if (!u_isUWhiteSpace(c)) return false; } return true; } // strcmp but ignoring case and accents -int compare(std::string const& a, std::string const& b) { - using namespace boost::locale; - return std::use_facet>(std::locale()).compare(collator_base::primary, a, b); -} - +int compare(std::string_view a, std::string_view b) { + UErrorCode err = U_ZERO_ERROR; + thread_local std::unique_ptr collator(icu::Collator::createInstance(err)); + collator->setStrength(icu::Collator::PRIMARY); + int result = collator->compareUTF8(a, b, err); + if (U_FAILURE(err)) throw agi::InternalError(u_errorName(err)); + return result; } +} // namespace -namespace agi { - -karaoke_match_result auto_match_karaoke(std::vector const& source_strings, std::string const& dest_string) { - karaoke_match_result result = { 0, 0 }; +agi::KaraokeMatchResult agi::AutoMatchKaraoke(std::vector const& source_strings, + std::string_view dest_string) { + KaraokeMatchResult result = {0, 0}; if (source_strings.empty()) return result; - - using namespace boost::locale::boundary; - using boost::starts_with; - result.source_length = 1; - ssegment_index destination_characters(character, begin(dest_string), end(dest_string)); - auto src = boost::to_lower_copy(source_strings[0]); - auto dst = destination_characters.begin(); - auto dst_end = destination_characters.end(); + + // Constructing an icu::BreakIterator is very expensive, so reuse them between calls + thread_local BreakIterator dst, src; + dst.set_text(dest_string); + std::string src_str(source_strings[0]); + boost::to_lower(src_str); + src.set_text(src_str); // Eat all the whitespace at the beginning of the source and destination // syllables and exit if either ran out. auto eat_whitespace = [&]() -> bool { - size_t i = 0, first_non_whitespace = 0; - while (is_whitespace(next_codepoint(src.c_str(), &i))) - first_non_whitespace = i; - if (first_non_whitespace) - src = src.substr(first_non_whitespace); - - while (dst != dst_end && is_whitespace(dst->str())) { - ++dst; + while (!src.done() && is_whitespace(src.current())) + src.next(); + while (!dst.done() && is_whitespace(dst.current())) { + dst.next(); ++result.destination_length; } // If we ran out of dest then this needs to match the rest of the // source syllables (this probably means the user did something wrong) - if (dst == dst_end) { + if (dst.done()) { result.source_length = source_strings.size(); return true; } - return src.empty(); + return src.done(); }; if (eat_whitespace()) return result; @@ -100,36 +100,34 @@ karaoke_match_result auto_match_karaoke(std::vector const& source_s // and destination. Check if the source starts with a romanized kana, and // if it does then check if the destination also has the appropriate // character. If it does, match them and repeat. - while (!src.empty()) { - // First check for a basic match of the first character of the source and dest - auto first_src_char = ssegment_index(character, begin(src), end(src)).begin()->str(); - if (compare(first_src_char, dst->str()) == 0) { - ++dst; + while (!src.done()) { + // First check for a basic match of the start of the source and dest + // Checking for src/dst done is handled by eat_whitespace() + while (compare(src.current(), dst.current()) == 0) { + src.next(); + dst.next(); ++result.destination_length; - src.erase(0, first_src_char.size()); if (eat_whitespace()) return result; - continue; } - auto check = [&](kana_pair const& kp) -> bool { - if (!starts_with(&*dst->begin(), kp.kana)) return false; - - src = src.substr(strlen(kp.romaji)); - for (size_t i = 0; kp.kana[i]; ) { - i += dst->length(); - ++result.destination_length; - ++dst; - } - return true; - }; - + // Check if the start of src is now a known romaji, and if so check + // if dst starts with any of the possible corresponding kana bool matched = false; - for (auto const& match : romaji_to_kana(src)) { - if (check(match)) { - if (eat_whitespace()) return result; - matched = true; - break; - } + for (auto [kana, romaji] : romaji_to_kana(src.current_to_end())) { + if (!dst.current_to_end().starts_with(kana)) continue; + + // romaji is always one byte per character + for (size_t i = 0; i < romaji.size(); ++i) + src.next(); + // FIXME: Valid for all locales? + size_t count = kana.size() / 3; + result.destination_length += count; + for (size_t i = 0; i < count; ++i) + dst.next(); + + if (eat_whitespace()) return result; + matched = true; + break; } if (!matched) break; } @@ -137,7 +135,7 @@ karaoke_match_result auto_match_karaoke(std::vector const& source_s // Source and dest are now non-empty and start with non-whitespace. // If there's only one character left in the dest, it obviously needs to // match all of the source syllables left. - if (std::distance(dst, dst_end) == 1) { + if (dst.is_last()) { result.source_length = source_strings.size(); ++result.destination_length; return result; @@ -161,26 +159,32 @@ karaoke_match_result auto_match_karaoke(std::vector const& source_s static const int dst_lookahead_max = 3; for (size_t lookahead = 0; lookahead < dst_lookahead_max; ++lookahead) { - if (++dst == dst_end) break; + dst.next(); + if (dst.done()) break; + auto cur = dst.current(); // Transliterate this character if it's a known hiragana or katakana character - std::vector translit; - auto next = std::next(dst); - if (next != dst_end) - boost::copy(kana_to_romaji(dst->str() + next->str()), back_inserter(translit)); - boost::copy(kana_to_romaji(dst->str()), back_inserter(translit)); + // Kana can be either one or two characters long, and both characters must + // be exactly 3 bytes long and start with 0xE3. + if (cur.size() != 3 || cur[0] != '\xE3') continue; + + std::vector translit; + if (dest_string.size() >= cur.data() - dest_string.data() + 6) + boost::copy(kana_to_romaji(std::string_view(cur.data(), 6)), back_inserter(translit)); + boost::copy(kana_to_romaji(dst.current()), back_inserter(translit)); // Search for it and the transliterated version in the source - int src_lookahead_max = (lookahead + 1) * max_character_length; - int src_lookahead_pos = 0; + size_t src_lookahead_max = (lookahead + 1) * max_character_length; + size_t src_lookahead_pos = 0; for (auto const& syl : source_strings) { // Don't count blank syllables in the max search distance if (is_whitespace(syl)) continue; if (++src_lookahead_pos == 1) continue; if (src_lookahead_pos > src_lookahead_max) break; - std::string lsyl = boost::to_lower_copy(syl); - if (!(starts_with(syl, dst->str()) || util::any_of(translit, [&](const char *str) { return starts_with(lsyl, str); }))) + std::string lsyl(syl); + boost::to_lower(lsyl); + if (!(syl.starts_with(cur) || util::any_of(translit, [&](auto s) { return lsyl.starts_with(s); }))) continue; // The syllable immediately after the current one matched, so @@ -204,4 +208,131 @@ karaoke_match_result auto_match_karaoke(std::vector const& source_s return result; } + +namespace agi { +void KaraokeMatcher::SetInputData(std::vector&& src, std::string&& dst) { + syllables = std::move(src); + matched_groups.clear(); + src_start = 0; + src_len = syllables.size() ? 1 : 0; + + destination_str = std::move(dst); + bi.set_text(destination_str); + for (; !bi.done(); bi.next()) + character_positions.push_back(bi.current().data() - destination_str.data()); + character_positions.push_back(destination_str.size()); + dst_start = 0; + dst_len = character_positions.size() > 1; +} + +std::string KaraokeMatcher::GetOutputLine() const { + std::string res; + + for (auto const& match : matched_groups) { + int duration = 0; + for (auto const& syl : match.src) + duration += syl.duration; + res += "{\\k" + std::to_string(duration / 10) + "}" + std::string(match.dst); + } + + return res; +} + +bool KaraokeMatcher::IncreaseSourceMatch() { + if (src_start + src_len < syllables.size()) { // FIXME: off-by-one? + ++src_len; + return true; + } + return false; +} + +bool KaraokeMatcher::DecreaseSourceMatch() { + if (src_len > 0) { + --src_len; + return true; + } + return false; +} + +bool KaraokeMatcher::IncreaseDestinationMatch() { + // +1 because there's one more entry in character_positions than there are + // characters + if (dst_start + dst_len + 1 < character_positions.size()) { + ++dst_len; + return true; + } + return false; +} + +bool KaraokeMatcher::DecreaseDestinationMatch() { + if (dst_len > 0) { + --dst_len; + return true; + } + return false; +} + +void KaraokeMatcher::AutoMatchJapanese() { + std::vector source; + for (size_t i = src_start; i < syllables.size(); ++i) + source.emplace_back(syllables[i].text); + size_t dst_pos = character_positions[dst_start]; + auto [src, dst] = AutoMatchKaraoke(source, std::string_view(destination_str).substr(dst_pos)); + src_len = src; + dst_len = dst; +} + +bool KaraokeMatcher::AcceptMatch() { + // Completely empty match + if (src_len == 0 && dst_len == 0) return false; + + matched_groups.push_back({CurrentSourceSelection(), CurrentDestinationSelection()}); + + src_start += src_len; + dst_start += dst_len; + src_len = 0; + dst_len = 0; + + IncreaseSourceMatch(); + IncreaseDestinationMatch(); + + return true; +} + +bool KaraokeMatcher::UndoMatch() { + if (matched_groups.empty()) return false; + + MatchGroup& group = matched_groups.back(); + src_start = group.src.data() - syllables.data(); + src_len = group.src.size(); + + auto dst_pos = group.dst.data() - destination_str.data(); + auto it = std::find(character_positions.begin(), character_positions.end(), dst_pos); + assert(it != character_positions.end()); + dst_start = it - character_positions.begin(); + + dst_pos = group.dst.data() + group.dst.size() - destination_str.data(); + auto it2 = std::find(character_positions.begin(), character_positions.end(), dst_pos); + assert(it2 != character_positions.end()); + dst_len = it2 - it; + + matched_groups.pop_back(); + + return true; +} + +std::span KaraokeMatcher::CurrentSourceSelection() const { + return std::span(syllables).subspan(src_start, src_len); +} +std::span KaraokeMatcher::UnmatchedSource() const { + return std::span(syllables).subspan(src_start + src_len); +} +std::string_view KaraokeMatcher::CurrentDestinationSelection() const { + return std::string_view(destination_str) + .substr(character_positions[dst_start], + character_positions[dst_start + dst_len] - character_positions[dst_start]); +} +std::string_view KaraokeMatcher::UnmatchedDestination() const { + return std::string_view(destination_str).substr(character_positions[dst_start + dst_len]); } +} // namespace agi diff --git a/libaegisub/common/keyframe.cpp b/libaegisub/common/keyframe.cpp index b2855bd11f..1917feff69 100644 --- a/libaegisub/common/keyframe.cpp +++ b/libaegisub/common/keyframe.cpp @@ -12,11 +12,6 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -/// @file keyframe.cpp -/// @see keyframe.h -/// @ingroup libaegisub -/// - #include "libaegisub/keyframe.h" #include @@ -24,7 +19,6 @@ #include "libaegisub/io.h" #include "libaegisub/line_iterator.h" -#include #include namespace { @@ -34,7 +28,7 @@ std::vector agi_keyframes(std::istream &file) { file >> fps_str; file >> fps; - return std::vector(agi::line_iterator(file), agi::line_iterator()); + return {agi::line_iterator(file), agi::line_iterator()}; } std::vector enumerated_keyframes(std::istream &file, char (*func)(std::string const&)) { @@ -95,8 +89,8 @@ int wwxd(std::string const& line) { } } -namespace agi { namespace keyframe { -void Save(agi::fs::path const& filename, std::vector const& keyframes) { +namespace agi::keyframe { +void Save(std::filesystem::path const& filename, std::vector const& keyframes) { io::Save file(filename); std::ostream& of = file.Get(); of << "# keyframe format v1" << std::endl; @@ -104,7 +98,7 @@ void Save(agi::fs::path const& filename, std::vector const& keyframes) { boost::copy(keyframes, std::ostream_iterator(of, "\n")); } -std::vector Load(agi::fs::path const& filename) { +std::vector Load(std::filesystem::path const& filename) { auto file = io::Open(filename); std::istream &is(*file); @@ -112,14 +106,14 @@ std::vector Load(agi::fs::path const& filename) { getline(is, header); if (header == "# keyframe format v1") return agi_keyframes(is); - if (boost::starts_with(header, "# XviD 2pass stat file")) return enumerated_keyframes(is, xvid); - if (boost::starts_with(header, "# ffmpeg 2-pass log file, using xvid codec")) return enumerated_keyframes(is, xvid); - if (boost::starts_with(header, "# avconv 2-pass log file, using xvid codec")) return enumerated_keyframes(is, xvid); - if (boost::starts_with(header, "##map version")) return enumerated_keyframes(is, divx); - if (boost::starts_with(header, "#options:")) return enumerated_keyframes(is, x264); - if (boost::starts_with(header, "# WWXD log file, using qpfile format")) return indexed_keyframes(is, wwxd); + if (header.starts_with("# XviD 2pass stat file")) return enumerated_keyframes(is, xvid); + if (header.starts_with("# ffmpeg 2-pass log file, using xvid codec")) return enumerated_keyframes(is, xvid); + if (header.starts_with("# avconv 2-pass log file, using xvid codec")) return enumerated_keyframes(is, xvid); + if (header.starts_with("##map version")) return enumerated_keyframes(is, divx); + if (header.starts_with("#options:")) return enumerated_keyframes(is, x264); + if (header.starts_with("# WWXD log file, using qpfile format")) return indexed_keyframes(is, wwxd); throw UnknownKeyframeFormatError("File header does not match any known formats"); } -} } +} diff --git a/libaegisub/common/line_iterator.cpp b/libaegisub/common/line_iterator.cpp index b288c831b6..73bf725d7a 100644 --- a/libaegisub/common/line_iterator.cpp +++ b/libaegisub/common/line_iterator.cpp @@ -19,16 +19,16 @@ namespace agi { -line_iterator_base::line_iterator_base(std::istream &stream, std::string encoding) +line_iterator_base::line_iterator_base(std::istream &stream, const char *encoding) : stream(&stream) { - boost::to_lower(encoding); - if (encoding != "utf-8") { - agi::charset::IconvWrapper c("utf-8", encoding.c_str()); - c.Convert("\r", 1, reinterpret_cast(&cr), sizeof(int)); - c.Convert("\n", 1, reinterpret_cast(&lf), sizeof(int)); - width = c.RequiredBufferSize("\n"); - conv = std::make_shared(encoding.c_str(), "utf-8"); + std::string_view e = encoding; + if (e != "utf-8" && e != "UTF-8") { + agi::charset::IconvWrapper c("utf-8", encoding); + c.Convert("\r", cr); + width = c.Convert("\n", lf); + conv = std::make_shared(encoding, "utf-8"); + assert(width != 0); } } @@ -45,30 +45,25 @@ bool line_iterator_base::getline(std::string &str) { str.pop_back(); } else { - union { - int32_t chr; - char buf[4]; - } u; - for (;;) { - u.chr = 0; - std::streamsize read = stream->rdbuf()->sgetn(u.buf, width); + std::array buf = {0, 0, 0, 0}; + std::streamsize read = stream->rdbuf()->sgetn(buf.data(), width); if (read < (std::streamsize)width) { for (int i = 0; i < read; i++) { - str += u.buf[i]; + str += buf[i]; } stream->setstate(std::ios::eofbit); break; } - if (u.chr == cr) continue; - if (u.chr == lf) break; + if (buf == cr) continue; + if (buf == lf) break; for (int i = 0; i < read; i++) { - str += u.buf[i]; + str += buf[i]; } } } - if (conv.get()) { + if (conv) { std::string tmp; conv->Convert(str, tmp); str = std::move(tmp); diff --git a/libaegisub/common/log.cpp b/libaegisub/common/log.cpp index 5750330218..8f5a050fb0 100644 --- a/libaegisub/common/log.cpp +++ b/libaegisub/common/log.cpp @@ -19,13 +19,12 @@ #include "libaegisub/dispatch.h" #include "libaegisub/util.h" -#include -#include -#include #include #include +#include +#include -namespace agi { namespace log { +namespace agi::log { /// Global log sink. LogSink *log; @@ -98,8 +97,8 @@ Message::~Message() { agi::log::log->Log(sm); } -JsonEmitter::JsonEmitter(fs::path const& directory) -: fp(new boost::filesystem::ofstream(unique_path(directory/util::strftime("%Y-%m-%d-%H-%M-%S-%%%%%%%%.json")))) +JsonEmitter::JsonEmitter(std::filesystem::path const& directory) +: fp(new std::ofstream(directory/util::strftime("%Y-%m-%d-%H-%M-%S.json"))) { } @@ -117,4 +116,4 @@ void JsonEmitter::log(SinkMessage const& sm) { fp->flush(); } -} } +} diff --git a/libaegisub/common/mru.cpp b/libaegisub/common/mru.cpp index dd7cfde92b..81db2715ab 100644 --- a/libaegisub/common/mru.cpp +++ b/libaegisub/common/mru.cpp @@ -23,7 +23,7 @@ #include "libaegisub/option_value.h" namespace { -const char *mru_names[] = { +std::string_view mru_names[] = { "Audio", "Find", "Keyframes", @@ -33,7 +33,7 @@ const char *mru_names[] = { "Video", }; -const char *option_names[] = { +std::string_view option_names[] = { "Limits/MRU", "Limits/Find Replace", "Limits/MRU", @@ -43,9 +43,9 @@ const char *option_names[] = { "Limits/MRU", }; -int mru_index(const char *key) { +int mru_index(std::string_view key) { int i; - switch (*key) { + switch (key[0]) { case 'A': i = 0; break; case 'F': i = 1; break; case 'K': i = 2; break; @@ -55,12 +55,12 @@ int mru_index(const char *key) { case 'V': i = 6; break; default: return -1; } - return strcmp(key, mru_names[i]) == 0 ? i : -1; + return key == mru_names[i] ? i : -1; } } namespace agi { -MRUManager::MRUManager(agi::fs::path const& config, std::pair default_config, agi::Options *options) +MRUManager::MRUManager(std::filesystem::path const& config, std::string_view default_config, agi::Options *options) : config_name(config) , options(options) { @@ -71,14 +71,14 @@ MRUManager::MRUManager(agi::fs::path const& config, std::pair= map->size()) throw MRUError("Requested element index is out of range."); @@ -115,7 +115,7 @@ void MRUManager::Flush() { json::Object out; for (size_t i = 0; i < mru.size(); ++i) { - json::Array &array = out[mru_names[i]]; + json::Array &array = out.emplace(mru_names[i], json::Array()).first->second; for (auto const& p : mru[i]) array.push_back(p.string()); } @@ -123,7 +123,7 @@ void MRUManager::Flush() { agi::JsonWriter::Write(out, io::Save(config_name).Get()); } -void MRUManager::Prune(const char *key, MRUListMap& map) const { +void MRUManager::Prune(std::string_view key, MRUListMap& map) const { size_t limit = 16u; if (options) { int idx = mru_index(key); @@ -133,7 +133,7 @@ void MRUManager::Prune(const char *key, MRUListMap& map) const { map.resize(std::min(limit, map.size())); } -void MRUManager::Load(const char *key, const json::Array& array) { +void MRUManager::Load(std::string_view key, const json::Array& array) { int idx = mru_index(key); if (idx == -1) return; diff --git a/libaegisub/common/option.cpp b/libaegisub/common/option.cpp index cdf5a07b89..13e93a3a03 100644 --- a/libaegisub/common/option.cpp +++ b/libaegisub/common/option.cpp @@ -12,10 +12,6 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -/// @file option.cpp -/// @brief Option interface. -/// @ingroup libaegisub - #include "libaegisub/option.h" #include "libaegisub/cajun/reader.h" @@ -28,9 +24,7 @@ #include "libaegisub/io.h" #include "libaegisub/log.h" #include "libaegisub/option_value.h" -#include "libaegisub/make_unique.h" -#include #include #include #include @@ -51,23 +45,22 @@ class ConfigVisitor final : public json::ConstVisitor { bool ignore_errors; void Error(const char *message) { - if (ignore_errors) - LOG_E("option/load/config_visitor") << "Error loading option from user configuration: " << message; - else + if (!ignore_errors) throw OptionJsonValueError(message); + LOG_E("option/load/config_visitor") << "Error loading option from user configuration: " << message; } template - void ReadArray(json::Array const& src, std::string const& array_type) { + void ReadArray(json::Array const& src) { typename OptionValueType::value_type arr; arr.reserve(src.size()); for (json::Object const& obj : src) - arr.push_back((typename OptionValueType::value_type::value_type)(obj.begin()->second)); + arr.emplace_back(static_cast((obj.begin()->second))); - values.push_back(agi::make_unique(name, std::move(arr))); + values.push_back(std::make_unique(name, std::move(arr))); } - void Visit(const json::Object& object) { + void Visit(const json::Object& object) override { auto old_name = name; for (auto const& obj : object) { name = old_name + (old_name.empty() ? "" : "/") + obj.first; @@ -76,7 +69,7 @@ class ConfigVisitor final : public json::ConstVisitor { name = old_name; } - void Visit(const json::Array& array) { + void Visit(const json::Array& array) override { if (array.empty()) return Error("Cannot infer the type of an empty array"); @@ -93,45 +86,45 @@ class ConfigVisitor final : public json::ConstVisitor { } if (array_type == "string") - ReadArray(array, array_type); + ReadArray(array); else if (array_type == "int") - ReadArray(array, array_type); + ReadArray(array); else if (array_type == "double") - ReadArray(array, array_type); + ReadArray(array); else if (array_type == "bool") - ReadArray(array, array_type); + ReadArray(array); else if (array_type == "color") - ReadArray(array, array_type); + ReadArray(array); else Error("Array type not handled"); } - void Visit(int64_t number) { - values.push_back(agi::make_unique(name, number)); + void Visit(int64_t number) override { + values.push_back(std::make_unique(name, number)); } - void Visit(double number) { - values.push_back(agi::make_unique(name, number)); + void Visit(double number) override { + values.push_back(std::make_unique(name, number)); } - void Visit(const json::String& string) { + void Visit(const json::String& string) override { size_t size = string.size(); if ((size == 4 && string[0] == '#') || (size == 7 && string[0] == '#') || - (size >= 10 && boost::starts_with(string, "rgb(")) || - ((size == 9 || size == 10) && boost::starts_with(string, "&H"))) + (size >= 10 && string.starts_with("rgb(")) || + ((size == 9 || size == 10) && string.starts_with("&H"))) { - values.push_back(agi::make_unique(name, string)); + values.push_back(std::make_unique(name, agi::Color(string))); } else { - values.push_back(agi::make_unique(name, string)); + values.push_back(std::make_unique(name, string)); } } - void Visit(bool boolean) { - values.push_back(agi::make_unique(name, boolean)); + void Visit(bool boolean) override { + values.push_back(std::make_unique(name, boolean)); } - void Visit(const json::Null& null) { + void Visit(const json::Null& null) override { Error("Attempt to read null value"); } @@ -169,11 +162,7 @@ struct option_name_cmp { return a->GetName() < b->GetName(); } - bool operator()(std::unique_ptr const& a, std::string const& b) const { - return a->GetName() < b; - } - - bool operator()(std::unique_ptr const& a, const char *b) const { + bool operator()(std::unique_ptr const& a, std::string_view b) const { return a->GetName() < b; } }; @@ -182,12 +171,12 @@ struct option_name_cmp { namespace agi { -Options::Options(agi::fs::path const& file, std::pair default_config, const OptionSetting setting) +Options::Options(std::filesystem::path const& file, std::string_view default_config, OptionSetting setting) : config_file(file) , setting(setting) { LOG_D("agi/options") << "New Options object"; - boost::interprocess::ibufferstream stream(default_config.first, default_config.second); + boost::interprocess::ibufferstream stream(default_config.data(), default_config.size()); LoadConfig(stream); } @@ -258,7 +247,7 @@ void Options::LoadConfig(std::istream& stream, bool ignore_errors) { } } -OptionValue *Options::Get(const char *name) { +OptionValue *Options::Get(std::string_view name) { auto index = lower_bound(begin(values), end(values), name, option_name_cmp()); if (index != end(values) && (*index)->GetName() == name) return index->get(); diff --git a/libaegisub/common/parser.cpp b/libaegisub/common/parser.cpp index ef53ed0d20..3dcc978906 100644 --- a/libaegisub/common/parser.cpp +++ b/libaegisub/common/parser.cpp @@ -19,20 +19,15 @@ #include "libaegisub/color.h" #include "libaegisub/ass/dialogue_parser.h" +#include +#include +#include +#include + #include -#include -#include -#include #include #include -// We have to use the copy of pheonix within spirit if it exists, as the -// standalone copy has different header guards -#ifdef HAVE_BOOST_SPIRIT_HOME_PHOENIX_VERSION_HPP -#include -#else -#include -#endif BOOST_FUSION_ADAPT_STRUCT( agi::Color, @@ -47,7 +42,7 @@ using namespace boost::spirit; /// Convert a abgr value in an int or unsigned int to an agi::Color struct unpack_colors : public boost::static_visitor { - template struct result { typedef agi::Color type; }; + template struct result { using type = agi::Color; }; template agi::Color operator()(T arg) const { return boost::apply_visitor(*this, arg); @@ -108,7 +103,7 @@ struct color_grammar : qi::grammar { template struct dialogue_tokens final : lex::lexer { - int paren_depth; + int paren_depth = 0; template void init(KT &&kara_templater) { @@ -120,7 +115,7 @@ struct dialogue_tokens final : lex::lexer { this->self = string("\\\\[nNh]", LINE_BREAK) - | char_('{', OVR_BEGIN)[ref(paren_depth) = 0, _state = "OVR"] + | char_('{', OVR_BEGIN)[(ref(paren_depth) = 0, _state = "OVR")] | kara_templater | string(".", TEXT) ; @@ -138,7 +133,7 @@ struct dialogue_tokens final : lex::lexer { = char_('{', ERROR) | char_('}', OVR_END)[_state = "INITIAL"] | char_('(', OPEN_PAREN)[++ref(paren_depth)] - | char_(')', CLOSE_PAREN)[--ref(paren_depth), if_(ref(paren_depth) == 0)[_state = "OVR"]] + | char_(')', CLOSE_PAREN)[(--ref(paren_depth), if_(ref(paren_depth) == 0)[_state = "OVR"])] | char_('\\', TAG_START)[_state = "TAGSTART"] | char_(',', ARG_SEP) | string("\\s+", WHITESPACE) @@ -158,8 +153,8 @@ struct dialogue_tokens final : lex::lexer { this->self("TAGNAME") = string("[a-z]+", TAG_NAME)[_state = "ARG"] - | char_('(', OPEN_PAREN)[++ref(paren_depth), _state = "ARG"] - | char_(')', CLOSE_PAREN)[--ref(paren_depth), if_(ref(paren_depth) == 0)[_state = "OVR"]] + | char_('(', OPEN_PAREN)[(++ref(paren_depth), _state = "ARG")] + | char_(')', CLOSE_PAREN)[(--ref(paren_depth), if_(ref(paren_depth) == 0)[_state = "OVR"])] | char_('}', OVR_END)[_state = "INITIAL"] | char_('\\', TAG_START)[_state = "TAGSTART"] | string(".", ARG)[_state = "ARG"] @@ -167,7 +162,7 @@ struct dialogue_tokens final : lex::lexer { ; } - dialogue_tokens(bool karaoke_templater) : paren_depth(0) { + dialogue_tokens(bool karaoke_templater) { using lex::string; using namespace agi::ass::DialogueTokenType; @@ -179,13 +174,13 @@ struct dialogue_tokens final : lex::lexer { }; template -bool do_try_parse(std::string const& str, Parser parser, T *out) { +bool do_try_parse(std::string_view str, Parser parser, T *out) { using namespace boost::spirit::qi; T res; - char const* cstr = str.c_str(); + auto cstr = str.data(), end = str.data() + str.size(); - bool parsed = parse(cstr, cstr + str.size(), parser, res); - if (parsed && cstr == &str[str.size()]) { + bool parsed = parse(cstr, end, parser, res); + if (parsed && cstr == end) { *out = res; return true; } @@ -197,20 +192,21 @@ bool do_try_parse(std::string const& str, Parser parser, T *out) { namespace agi { namespace parser { - bool parse(Color &dst, std::string const& str) { - std::string::const_iterator begin = str.begin(); - bool parsed = parse(begin, str.end(), color_grammar(), dst); - return parsed && begin == str.end(); + bool parse(Color &dst, std::string_view str) { + if (str.empty()) return false; + auto begin = str.data(), end = str.data() + str.size(); + bool parsed = parse(begin, end, color_grammar(), dst); + return parsed && begin == end; } } namespace ass { - std::vector TokenizeDialogueBody(std::string const& str, bool karaoke_templater) { + std::vector TokenizeDialogueBody(std::string_view str, bool karaoke_templater) { static const dialogue_tokens> kt(true); static const dialogue_tokens> not_kt(false); auto const& tokenizer = karaoke_templater ? kt : not_kt; - char const *first = str.c_str(); + char const *first = str.data(); char const *last = first + str.size(); std::vector data; auto it = tokenizer.begin(first, last), end = tokenizer.end(); @@ -231,11 +227,11 @@ namespace ass { namespace util { // from util.h - bool try_parse(std::string const& str, double *out) { + bool try_parse(std::string_view str, double *out) { return do_try_parse(str, boost::spirit::qi::double_, out); } - bool try_parse(std::string const& str, int *out) { + bool try_parse(std::string_view str, int *out) { return do_try_parse(str, boost::spirit::qi::int_, out); } } diff --git a/libaegisub/common/parser.h b/libaegisub/common/parser.h index d83ffb0bd6..49cae2ed2d 100644 --- a/libaegisub/common/parser.h +++ b/libaegisub/common/parser.h @@ -12,7 +12,7 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -#include +#include namespace agi { struct Color; @@ -34,6 +34,6 @@ namespace agi { /// * CSS-style rgb(r,g,b) /// /// CSS's rgb(r%,g%,b%) format is not currently supported. - bool parse(Color &dst, std::string const& str); + bool parse(Color &dst, std::string_view str); } } diff --git a/libaegisub/common/path.cpp b/libaegisub/common/path.cpp index cca7302cc5..a98ca5cc92 100644 --- a/libaegisub/common/path.cpp +++ b/libaegisub/common/path.cpp @@ -18,11 +18,10 @@ #include "libaegisub/fs.h" -#include #include namespace { -static const char *tokens[] = { +constexpr std::string_view tokens[] = { "?audio", "?data", "?dictionary", @@ -33,8 +32,8 @@ static const char *tokens[] = { "?video" }; -int find_token(const char *str, size_t len) { - if (len < 5 || str[0] != '?') return -1; +int find_token(std::string_view str) { + if (str.size() < 5 || str[0] != '?') return -1; int idx; switch (str[1] + str[4]) { case 'a' + 'i': idx = 0; break; @@ -47,8 +46,12 @@ int find_token(const char *str, size_t len) { case 'v' + 'e': idx = 7; break; default: return -1; } - - return strncmp(str, tokens[idx], strlen(tokens[idx])) == 0 ? idx : -1; + return str.starts_with(tokens[idx]) ? idx : -1; +} +int checked_find_token(std::string_view str) { + int idx = find_token(str); + if (idx == -1) throw agi::InternalError("Bad token: " + std::string(str)); + return idx; } } @@ -60,25 +63,25 @@ Path::Path() { FillPlatformSpecificPaths(); } -fs::path Path::Decode(std::string const& path) const { - int idx = find_token(path.c_str(), path.size()); +fs::path Path::Decode(std::string_view path) const { + int idx = find_token(path); if (idx == -1 || paths[idx].empty()) return fs::path(path).make_preferred(); - return (paths[idx]/path.substr(strlen(tokens[idx]))).make_preferred(); + path = path.substr(tokens[idx].size()); + if (path.size() && path[0] == '/') path.remove_prefix(1); + if (path.empty()) return paths[idx]; + return (paths[idx]/path).make_preferred(); } -fs::path Path::MakeRelative(fs::path const& path, std::string const& token) const { - int idx = find_token(token.c_str(), token.size()); - if (idx == -1) throw agi::InternalError("Bad token: " + token); - - return MakeRelative(path, paths[idx]); +fs::path Path::MakeRelative(fs::path const& path, std::string_view token) const { + return MakeRelative(path, paths[checked_find_token(token)]); } fs::path Path::MakeRelative(fs::path const& path, fs::path const& base) const { if (path.empty() || base.empty()) return path; const auto str = path.string(); - if (boost::starts_with(str, "?dummy") || boost::starts_with(str, "dummy-audio:")) + if (str.starts_with("?dummy") || str.starts_with("dummy-audio:")) return path; // Paths on different volumes can't be made relative to each other @@ -89,7 +92,7 @@ fs::path Path::MakeRelative(fs::path const& path, fs::path const& base) const { auto ref_it = base.begin(); for (; path_it != path.end() && ref_it != base.end() && *path_it == *ref_it; ++path_it, ++ref_it) ; - agi::fs::path result; + std::filesystem::path result; for (; ref_it != base.end(); ++ref_it) result /= ".."; for (; path_it != path.end(); ++path_it) @@ -98,14 +101,13 @@ fs::path Path::MakeRelative(fs::path const& path, fs::path const& base) const { return result; } -fs::path Path::MakeAbsolute(fs::path path, std::string const& token) const { +fs::path Path::MakeAbsolute(fs::path path, std::string_view token) const { if (path.empty()) return path; - int idx = find_token(token.c_str(), token.size()); - if (idx == -1) throw agi::InternalError("Bad token: " + token); + int idx = checked_find_token(token); path.make_preferred(); const auto str = path.string(); - if (boost::starts_with(str, "?dummy") || boost::starts_with(str, "dummy-audio:")) + if (str.starts_with("?dummy") || str.starts_with("dummy-audio:")) return path; return (paths[idx].empty() || path.is_absolute()) ? path : paths[idx]/path; } @@ -128,9 +130,8 @@ std::string Path::Encode(fs::path const& path) const { return shortest; } -void Path::SetToken(const char *token_name, fs::path const& token_value) { - int idx = find_token(token_name, strlen(token_name)); - if (idx == -1) throw agi::InternalError("Bad token: " + std::string(token_name)); +void Path::SetToken(std::string_view token_name, fs::path const& token_value) { + int idx = checked_find_token(token_name); if (token_value.empty()) paths[idx] = token_value; diff --git a/libaegisub/common/thesaurus.cpp b/libaegisub/common/thesaurus.cpp index 9d914be773..23366e82c5 100644 --- a/libaegisub/common/thesaurus.cpp +++ b/libaegisub/common/thesaurus.cpp @@ -12,24 +12,19 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -/// @file thesaurus.cpp -/// @brief MyThes-compatible thesaurus implementation -/// @ingroup libaegisub thesaurus - #include "libaegisub/thesaurus.h" #include "libaegisub/charset_conv.h" #include "libaegisub/file_mapping.h" #include "libaegisub/line_iterator.h" -#include "libaegisub/make_unique.h" #include "libaegisub/split.h" #include namespace agi { -Thesaurus::Thesaurus(agi::fs::path const& dat_path, agi::fs::path const& idx_path) -: dat(make_unique(dat_path)) +Thesaurus::Thesaurus(std::filesystem::path const& dat_path, std::filesystem::path const& idx_path) +: dat(std::make_unique(dat_path)) { read_file_mapping idx_file(idx_path); boost::interprocess::ibufferstream idx(idx_file.read(), static_cast(idx_file.size())); @@ -39,19 +34,19 @@ Thesaurus::Thesaurus(agi::fs::path const& dat_path, agi::fs::path const& idx_pat std::string unused_entry_count; getline(idx, unused_entry_count); - conv = make_unique(encoding_name.c_str(), "utf-8"); + conv = std::make_unique(encoding_name.c_str(), "utf-8"); // Read the list of words and file offsets for those words - for (auto const& line : line_iterator(idx, encoding_name)) { + for (auto const& line : line_iterator(idx, encoding_name.c_str())) { auto pos = line.find('|'); if (pos != line.npos && line.find('|', pos + 1) == line.npos) offsets[line.substr(0, pos)] = static_cast(atoi(line.c_str() + pos + 1)); } } -Thesaurus::~Thesaurus() { } +Thesaurus::~Thesaurus() = default; -std::vector Thesaurus::Lookup(std::string const& word) { +std::vector Thesaurus::Lookup(std::string_view word) { std::vector out; if (!dat) return out; @@ -64,26 +59,26 @@ std::vector Thesaurus::Lookup(std::string const& word) { auto buff_end = buff + len; std::string temp; - auto read_line = [&] (std::string& temp) -> std::string * { + auto read_line = [&]() -> std::string_view { auto start = buff; auto end = std::find(buff, buff_end, '\n'); buff = end < buff_end ? end + 1 : buff_end; if (end > start && end[-1] == '\r') --end; temp.clear(); - conv->Convert(start, end - start, temp); - return &temp; + conv->Convert(std::string_view(start, end - start), temp); + return temp; }; // First line is the word and meaning count std::vector header; - agi::Split(header, *read_line(temp), '|'); + agi::Split(header, read_line(), '|'); if (header.size() != 2) return out; int meanings = atoi(header[1].c_str()); out.reserve(meanings); std::vector line; for (int i = 0; i < meanings; ++i) { - agi::Split(line, *read_line(temp), '|'); + agi::Split(line, read_line(), '|'); if (line.size() < 2) continue; diff --git a/libaegisub/include/libaegisub/make_unique.h b/libaegisub/common/unicode.cpp similarity index 53% rename from libaegisub/include/libaegisub/make_unique.h rename to libaegisub/common/unicode.cpp index 3af862e97b..f9508c2def 100644 --- a/libaegisub/include/libaegisub/make_unique.h +++ b/libaegisub/common/unicode.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014, Thomas Goyne +// Copyright (c) 2022, Thomas Goyne // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -14,11 +14,25 @@ // // Aegisub Project http://www.aegisub.org/ -#include +#include "libaegisub/unicode.h" -namespace agi { - template - std::unique_ptr make_unique(Args&&... args) { - return std::unique_ptr(new T(std::forward(args)...)); - } +#include "libaegisub/exception.h" + +using namespace agi; + +BreakIterator::BreakIterator() { + UErrorCode err = U_ZERO_ERROR; + bi.reset(icu::BreakIterator::createCharacterInstance(icu::Locale::getDefault(), err)); + if (U_FAILURE(err)) throw agi::InternalError(u_errorName(err)); +} + +void BreakIterator::set_text(std::string_view new_str) { + UErrorCode err = U_ZERO_ERROR; + UTextPtr ut(utext_openUTF8(nullptr, new_str.data(), new_str.size(), &err)); + bi->setText(ut.get(), err); + if (U_FAILURE(err)) throw agi::InternalError(u_errorName(err)); + + str = new_str; + begin = 0; + end = bi->next(); } diff --git a/libaegisub/common/util.cpp b/libaegisub/common/util.cpp index 45c01ca44d..9a32e6e1b4 100644 --- a/libaegisub/common/util.cpp +++ b/libaegisub/common/util.cpp @@ -17,33 +17,22 @@ #include "libaegisub/util.h" #include "libaegisub/util_osx.h" +#include "libaegisub/exception.h" + #include #include +#include #include #include +#include +#include +#include +#include +#include namespace { const size_t bad_pos = (size_t)-1; -const std::pair bad_match(bad_pos, bad_pos); - -template -size_t advance_both(Iterator& folded, Iterator& raw) { - size_t len; - if (*folded == *raw) { - len = folded->length(); - ++folded; - } - else { - // This character was changed by case folding, so refold it and eat the - // appropriate number of characters from folded - len = boost::locale::fold_case(raw->str()).size(); - for (size_t folded_consumed = 0; folded_consumed < len; ++folded) - folded_consumed += folded->length(); - } - - ++raw; - return len; -} +const std::pair bad_match(bad_pos, bad_pos); std::pair find_range(std::string const& haystack, std::string const& needle, size_t start = 0) { const size_t match_start = haystack.find(needle, start); @@ -83,66 +72,31 @@ std::string strftime(const char *fmt, const tm *tmptr) { return buff; } +std::string fold_case(std::string_view str, icu::Edits *edits) { + if (str.size() > std::numeric_limits::max()) + throw InvalidInputException("String is too long for case folding"); + auto size = static_cast(str.size()); + UErrorCode err = U_ZERO_ERROR; + std::string ret; + icu::StringByteSink sink(&ret, size); + icu::CaseMap::utf8Fold(0, icu::StringPiece(str.data(), size), sink, edits, err); + if (U_FAILURE(err)) throw InvalidInputException(u_errorName(err)); + return ret; +} + std::pair ifind(std::string const& haystack, std::string const& needle) { - const auto folded_hs = boost::locale::fold_case(haystack); - const auto folded_n = boost::locale::fold_case(needle); + icu::Edits edits; + const auto folded_hs = fold_case(haystack, &edits); + const auto folded_n = fold_case(needle, nullptr); auto match = find_range(folded_hs, folded_n); - if (match == bad_match || folded_hs == haystack) + if (match == bad_match || !edits.hasChanges()) return match; - - // We have a match, but the position is an index into the folded string - // and we want an index into the unfolded string. - - using namespace boost::locale::boundary; - const ssegment_index haystack_characters(character, begin(haystack), end(haystack)); - const ssegment_index folded_characters(character, begin(folded_hs), end(folded_hs)); - const size_t haystack_char_count = boost::distance(haystack_characters); - const size_t folded_char_count = boost::distance(folded_characters); - - // As of Unicode 6.2, case folding can never reduce the number of - // characters, and can only reduce the number of bytes with UTF-8 when - // increasing the number of characters. As a result, iff the bytes and - // characters are unchanged, no folds changed the size of any characters - // and our indices are correct. - if (haystack.size() == folded_hs.size() && haystack_char_count == folded_char_count) - return match; - - const auto map_folded_to_raw = [&]() -> std::pair { - size_t start = -1; - - // Iterate over each pair of characters and refold each character which was - // changed by folding, so that we can find the corresponding positions in - // the unfolded string - auto folded_it = begin(folded_characters); - auto haystack_it = begin(haystack_characters); - size_t folded_pos = 0; - - while (folded_pos < match.first) - folded_pos += advance_both(folded_it, haystack_it); - // If we overshot the start then the match started in the middle of a - // character which was folded to multiple characters - if (folded_pos > match.first) - return bad_match; - - start = distance(begin(haystack), begin(*haystack_it)); - - while (folded_pos < match.second) - folded_pos += advance_both(folded_it, haystack_it); - if (folded_pos > match.second) - return bad_match; - - return {start, distance(begin(haystack), begin(*haystack_it))}; - }; - - auto ret = map_folded_to_raw(); - while (ret == bad_match) { - // Found something, but it was an invalid match so retry from the next character - match = find_range(folded_hs, folded_n, match.first + 1); - if (match == bad_match) return match; - ret = map_folded_to_raw(); - } - - return ret; + auto it = edits.getFineIterator(); + UErrorCode err = U_ZERO_ERROR; + match.first = it.sourceIndexFromDestinationIndex(static_cast(match.first), err); + match.second = it.sourceIndexFromDestinationIndex(static_cast(match.second), err); + if (U_FAILURE(err)) throw InvalidInputException(u_errorName(err)); + return match; } std::string tagless_find_helper::strip_tags(std::string const& str, size_t s) { @@ -193,6 +147,14 @@ void tagless_find_helper::map_range(size_t &s, size_t &e) { e += block.second - block.first; } } + +void InitLocale() { + // FIXME: need to verify we actually got a utf-8 locale + auto id = boost::locale::util::get_system_locale(true); + UErrorCode err = U_ZERO_ERROR; + icu::Locale::setDefault(icu::Locale::createCanonical(id.c_str()), err); + if (U_FAILURE(err)) throw InternalError(u_errorName(err)); +} } // namespace util #ifndef __APPLE__ diff --git a/libaegisub/common/vfr.cpp b/libaegisub/common/vfr.cpp index c82fcdbeed..1105a1c8ce 100644 --- a/libaegisub/common/vfr.cpp +++ b/libaegisub/common/vfr.cpp @@ -57,14 +57,14 @@ struct TimecodeRange { int start; int end; double fps; - bool operator<(TimecodeRange const& cmp) const { return start < cmp.start; } + auto operator<=>(TimecodeRange const& cmp) const { return start <=> cmp.start; } }; /// @brief Parse a single line of a v1 timecode file /// @param str Line to parse /// @return The line in TimecodeRange form, or TimecodeRange() if it's a comment TimecodeRange v1_parse_line(std::string const& str) { - if (str.empty() || str[0] == '#') return TimecodeRange(); + if (str.empty() || str[0] == '#') return {}; boost::interprocess::ibufferstream ss(str.data(), str.size()); TimecodeRange range; @@ -130,7 +130,7 @@ int64_t v1_parse(line_iterator file, std::string line, std::vector< } } -namespace agi { namespace vfr { +namespace agi::vfr { Framerate::Framerate(double fps) : denominator(default_denominator) , numerator(int64_t(fps * denominator)) @@ -171,28 +171,28 @@ Framerate::Framerate(std::initializer_list timecodes) SetFromTimecodes(); } -Framerate::Framerate(fs::path const& filename) +Framerate::Framerate(std::filesystem::path const& filename) : denominator(default_denominator) { auto file = agi::io::Open(filename); auto encoding = agi::charset::Detect(filename); - auto line = *line_iterator(*file, encoding); + auto line = *line_iterator(*file, encoding.c_str()); if (line == "# timecode format v2") { - copy(line_iterator(*file, encoding), line_iterator(), back_inserter(timecodes)); + copy(line_iterator(*file, encoding.c_str()), line_iterator(), back_inserter(timecodes)); SetFromTimecodes(); return; } if (line == "# timecode format v1" || line.substr(0, 7) == "Assume ") { if (line[0] == '#') - line = *line_iterator(*file, encoding); - numerator = v1_parse(line_iterator(*file, encoding), line, timecodes, last); + line = *line_iterator(*file, encoding.c_str()); + numerator = v1_parse(line_iterator(*file, encoding.c_str()), line, timecodes, last); return; } throw UnknownFormat(line); } -void Framerate::Save(fs::path const& filename, int length) const { +void Framerate::Save(std::filesystem::path const& filename, int length) const { agi::io::Save file(filename); auto &out = file.Get(); @@ -321,4 +321,4 @@ int Framerate::TimeAtSmpte(int h, int m, int s, int f) const { return TimeAtFrame(FrameAtSmpte(h, m, s, f)); } -} } +} diff --git a/libaegisub/include/lagi_pre.h b/libaegisub/include/lagi_pre.h index 7dcf1cb2e6..1a85a0d6be 100644 --- a/libaegisub/include/lagi_pre.h +++ b/libaegisub/include/lagi_pre.h @@ -18,6 +18,7 @@ #endif #include +#include #include #include #include @@ -34,8 +35,5 @@ #include #include #include -#define BOOST_NO_SCOPED_ENUMS -#include -#undef BOOST_NO_SCOPED_ENUMS #include #endif diff --git a/libaegisub/include/libaegisub/access.h b/libaegisub/include/libaegisub/access.h index 9916fa33e0..08a5f281fc 100644 --- a/libaegisub/include/libaegisub/access.h +++ b/libaegisub/include/libaegisub/access.h @@ -12,9 +12,9 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -#include +#include -namespace agi { namespace acs { +namespace agi::acs { enum Type { FileRead, DirRead, @@ -22,11 +22,11 @@ enum Type { DirWrite }; -void Check(fs::path const& file, acs::Type); +void Check(std::filesystem::path const& file, acs::Type); -static inline void CheckFileRead(fs::path const& file) { Check(file, acs::FileRead); } -static inline void CheckFileWrite(fs::path const& file) { Check(file, acs::FileWrite); } +static inline void CheckFileRead(std::filesystem::path const& file) { Check(file, acs::FileRead); } +static inline void CheckFileWrite(std::filesystem::path const& file) { Check(file, acs::FileWrite); } -static inline void CheckDirRead(fs::path const& dir) { Check(dir, acs::DirRead); } -static inline void CheckDirWrite(fs::path const& dir) { Check(dir, acs::DirWrite); } -} } +static inline void CheckDirRead(std::filesystem::path const& dir) { Check(dir, acs::DirRead); } +static inline void CheckDirWrite(std::filesystem::path const& dir) { Check(dir, acs::DirWrite); } +} diff --git a/libaegisub/include/libaegisub/ass/dialogue_parser.h b/libaegisub/include/libaegisub/ass/dialogue_parser.h index 0aa3f9962b..19c3f61b77 100644 --- a/libaegisub/include/libaegisub/ass/dialogue_parser.h +++ b/libaegisub/include/libaegisub/ass/dialogue_parser.h @@ -69,15 +69,17 @@ namespace agi { }; /// Tokenize the passed string as the body of a dialogue line - std::vector TokenizeDialogueBody(std::string const& str, bool karaoke_templater=false); + std::vector TokenizeDialogueBody(std::string_view str, bool karaoke_templater=false); /// Convert the body of drawings to DRAWING tokens - void MarkDrawings(std::string const& str, std::vector &tokens); + void MarkDrawings(std::string_view str, std::vector &tokens); /// Split the words in the TEXT tokens of the lexed line into their /// own tokens and convert the body of drawings to DRAWING tokens - void SplitWords(std::string const& str, std::vector &tokens); + void SplitWords(std::string_view str, std::vector &tokens); - std::vector SyntaxHighlight(std::string const& text, std::vector const& tokens, SpellChecker *spellchecker); + std::vector SyntaxHighlight(std::string_view text, + std::vector const& tokens, + SpellChecker *spellchecker); } } diff --git a/libaegisub/include/libaegisub/ass/karaoke.h b/libaegisub/include/libaegisub/ass/karaoke.h new file mode 100644 index 0000000000..782657bfc9 --- /dev/null +++ b/libaegisub/include/libaegisub/ass/karaoke.h @@ -0,0 +1,83 @@ +// Copyright (c) 2022, Thomas Goyne +// +// Permission to use, copy, modify, and 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. +// +// Aegisub Project http://www.aegisub.org/ + +#pragma once + +#include + +#include +#include +#include +#include + +namespace agi::ass { +/// Parsed syllable data +struct KaraokeSyllable { + int start_time; ///< Start time relative to time zero (not line start) in milliseconds + int duration; ///< Duration in milliseconds + std::string text; ///< Stripped syllable text + std::string tag_type; ///< \k, \kf or \ko + /// Non-karaoke override tags in this syllable. Key is an index in text + /// before which the value should be inserted + std::map ovr_tags; + + /// Get the text of this line with override tags and optionally the karaoke tag + std::string GetText(bool k_tag) const; + + friend bool operator==(KaraokeSyllable const&, KaraokeSyllable const&) = default; +}; + +class Karaoke { + std::vector syls; + agi::signal::Signal<> AnnounceSyllablesChanged; + + void DoAddSplit(size_t syl_idx, size_t pos); + /// Normalize the syllables so that the total duration is equal to the line length + void Normalize(int end_time); + + /// Add karaoke splits at each space + void AutoSplit(); + +public: + void SetLine(std::vector&& syls, bool auto_split, std::optional end_time); + + /// Add a split before character pos in syllable syl_idx + void AddSplit(size_t syl_idx, size_t pos); + /// Remove the split at the given index + void RemoveSplit(size_t syl_idx); + /// Set the start time of a syllable in ms + void SetStartTime(size_t syl_idx, int time); + /// Adjust the line's start and end times without shifting the syllables + void SetLineTimes(int start_time, int end_time); + + using iterator = std::vector::const_iterator; + iterator begin() const { return syls.begin(); } + iterator end() const { return syls.end(); } + size_t size() const { return syls.size(); } + + /// Get the line's text with k tags + std::string GetText() const; + + /// Get the karaoke tag type used, with leading slash + /// @returns "\k", "\kf", or "\ko" + std::string_view GetTagType() const; + /// Set the tag type for all karaoke tags in this line + void SetTagType(std::string_view new_type); + + DEFINE_SIGNAL_ADDERS(AnnounceSyllablesChanged, AddSyllablesChangedListener) +}; + +} // namespace agi::ass diff --git a/libaegisub/include/libaegisub/ass/time.h b/libaegisub/include/libaegisub/ass/time.h index 622052ceae..ef0a6c26fd 100644 --- a/libaegisub/include/libaegisub/ass/time.h +++ b/libaegisub/include/libaegisub/ass/time.h @@ -17,6 +17,7 @@ #pragma once #include +#include namespace agi { class Time { @@ -25,7 +26,7 @@ class Time { public: Time(int ms = 0); - Time(std::string const& text); + Time(std::string_view text); /// Get millisecond, rounded to centisecond precision // Always round up for 5ms because the range is [start, stop) diff --git a/libaegisub/include/libaegisub/ass/uuencode.h b/libaegisub/include/libaegisub/ass/uuencode.h index 98e37b44d5..c62ad0c5d1 100644 --- a/libaegisub/include/libaegisub/ass/uuencode.h +++ b/libaegisub/include/libaegisub/ass/uuencode.h @@ -17,10 +17,10 @@ #include #include -namespace agi { namespace ass { +namespace agi::ass { /// Encode a blob of data, using ASS's nonstandard variant std::string UUEncode(const char *begin, const char *end, bool insert_linebreaks=true); /// Decode an ASS uuencoded string std::vector UUDecode(const char *begin, const char *end); -} } +} diff --git a/libaegisub/include/libaegisub/audio/provider.h b/libaegisub/include/libaegisub/audio/provider.h index 70460a7234..bdd1359d97 100644 --- a/libaegisub/include/libaegisub/audio/provider.h +++ b/libaegisub/include/libaegisub/audio/provider.h @@ -17,9 +17,9 @@ #pragma once #include -#include #include +#include #include #include @@ -84,13 +84,14 @@ DEFINE_EXCEPTION(AudioDataNotFound, AudioProviderError); class BackgroundRunner; -std::unique_ptr CreateDummyAudioProvider(fs::path const& filename, BackgroundRunner *); -std::unique_ptr CreatePCMAudioProvider(fs::path const& filename, BackgroundRunner *); +std::unique_ptr CreateDummyAudioProvider(std::filesystem::path const& filename, BackgroundRunner *); +std::unique_ptr CreatePCMAudioProvider(std::filesystem::path const& filename, BackgroundRunner *); std::unique_ptr CreateConvertAudioProvider(std::unique_ptr source_provider); std::unique_ptr CreateLockAudioProvider(std::unique_ptr source_provider); -std::unique_ptr CreateHDAudioProvider(std::unique_ptr source_provider, fs::path const& dir); +std::unique_ptr CreateHDAudioProvider(std::unique_ptr source_provider, + std::filesystem::path const& dir); std::unique_ptr CreateRAMAudioProvider(std::unique_ptr source_provider); -void SaveAudioClip(AudioProvider const& provider, fs::path const& path, int start_time, int end_time); +void SaveAudioClip(AudioProvider const& provider, std::filesystem::path const& path, int start_time, int end_time); } diff --git a/libaegisub/include/libaegisub/cajun/elements.h b/libaegisub/include/libaegisub/cajun/elements.h index 1ba4b06960..392a26feda 100644 --- a/libaegisub/include/libaegisub/cajun/elements.h +++ b/libaegisub/include/libaegisub/cajun/elements.h @@ -11,8 +11,9 @@ Author: Terry Caton #include #include #include -#include #include +#include +#include #include namespace json { @@ -28,16 +29,15 @@ typedef double Double; typedef bool Boolean; typedef std::string String; typedef std::vector Array; -typedef std::map Object; +typedef std::map> Object; struct Null; - ///////////////////////////////////////////////////////////////////////// // Exception - base class for all JSON-related runtime errors class Exception : public std::runtime_error { public: - Exception(const std::string& sMessage) : std::runtime_error(sMessage) { } + Exception(const std::string& sMessage) : std::runtime_error(sMessage) { } }; ///////////////////////////////////////////////////////////////////////// @@ -55,8 +55,8 @@ class Exception : public std::runtime_error { // String str = objInvoices[1]["Customer"]["Company"]; class UnknownElement { public: - UnknownElement(); - UnknownElement(UnknownElement&& unknown); + UnknownElement() noexcept; + UnknownElement(UnknownElement&& unknown) noexcept; UnknownElement(Object object); UnknownElement(Array array); UnknownElement(double number); @@ -65,11 +65,12 @@ class UnknownElement { UnknownElement(bool boolean); UnknownElement(const char *string); UnknownElement(String string); + UnknownElement(std::string_view string); UnknownElement(Null null); ~UnknownElement(); - UnknownElement& operator=(UnknownElement&& unknown); + UnknownElement& operator=(UnknownElement&& unknown) noexcept; // implicit cast to actual element type. throws on failure operator Object const&() const; @@ -86,6 +87,7 @@ class UnknownElement { operator Boolean&(); operator String&(); operator Null&(); + operator std::string_view(); // implements visitor pattern void Accept(ConstVisitor& visitor) const; diff --git a/libaegisub/include/libaegisub/character_count.h b/libaegisub/include/libaegisub/character_count.h index 45ce53ce5f..8a4fa53322 100644 --- a/libaegisub/include/libaegisub/character_count.h +++ b/libaegisub/include/libaegisub/character_count.h @@ -14,7 +14,7 @@ // // Aegisub Project http://www.aegisub.org/ -#include +#include namespace agi { enum { @@ -25,11 +25,10 @@ namespace agi { }; /// Get the length in characters of the longest line in the given text - size_t MaxLineLength(std::string const& text, int ignore_mask); + size_t MaxLineLength(std::string_view text, int ignore_mask); /// Get the total number of characters in the string - size_t CharacterCount(std::string const& str, int ignore_mask); - size_t CharacterCount(std::string::const_iterator begin, std::string::const_iterator end, int ignore_mask); + size_t CharacterCount(std::string_view str, int ignore_mask); /// Get index in bytes of the nth character in str, or str.size() if str /// has less than n characters - size_t IndexOfCharacter(std::string const& str, size_t n); + size_t IndexOfCharacter(std::string_view str, size_t n); } diff --git a/libaegisub/include/libaegisub/charset.h b/libaegisub/include/libaegisub/charset.h index c206e64760..c8a5a00b9c 100644 --- a/libaegisub/include/libaegisub/charset.h +++ b/libaegisub/include/libaegisub/charset.h @@ -12,12 +12,7 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -/// @file charset.h -/// @brief Character set detection and manipulation utilities. -/// @ingroup libaegisub - -#include - +#include #include namespace agi { @@ -27,7 +22,7 @@ namespace agi { /// @brief Returns the character set with the highest confidence /// @param file File to check /// @return Detected character set. -std::string Detect(agi::fs::path const& file); +std::string Detect(std::filesystem::path const& file); } // namespace util } // namespace agi diff --git a/libaegisub/include/libaegisub/charset_conv.h b/libaegisub/include/libaegisub/charset_conv.h index c283c36caa..7563ebf46b 100644 --- a/libaegisub/include/libaegisub/charset_conv.h +++ b/libaegisub/include/libaegisub/charset_conv.h @@ -12,20 +12,17 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -/// @file charset_conv.h -/// @brief Wrapper for libiconv to present a more C++-friendly API -/// @ingroup libaegisub - #pragma once #include +#include #include +#include #include #include -namespace agi { - namespace charset { +namespace agi::charset { DEFINE_EXCEPTION(ConvError, Exception); DEFINE_EXCEPTION(UnsupportedConversion, ConvError); @@ -59,6 +56,7 @@ class Iconv { struct Converter { virtual ~Converter() = default; virtual size_t Convert(const char** inbuf, size_t* inbytesleft, char** outbuf, size_t* outbytesleft) = 0; + static std::unique_ptr create(bool subst, const char *src, const char *dst); }; /// @brief A C++ wrapper for iconv @@ -74,7 +72,7 @@ class IconvWrapper { /// @param enableSubst If true, when possible characters will be /// mutilated or dropped rather than a letting a /// conversion fail - IconvWrapper(const char* sourceEncoding, const char* destEncoding, bool enableSubst = true); + IconvWrapper(const char *sourceEncoding, const char *destEncoding, bool enableSubst = true); ~IconvWrapper(); /// @brief Convert a string from the source to destination charset @@ -82,31 +80,16 @@ class IconvWrapper { /// @return Converted string. Note that std::string always uses a single byte /// terminator, so c_str() may not return a valid string if the dest /// charset has wider terminators - std::string Convert(std::string const& source) { return Convert(source.c_str(), source.size()); } - std::string Convert(const char *source, size_t len); + std::string Convert(std::string_view source); /// @brief Convert a string from the source to destination charset /// @param source String to convert /// @param[out] dest String to place the result in - void Convert(std::string const& source, std::string &dest) { Convert(source.c_str(), source.size(), dest); } - void Convert(const char *source, size_t len, std::string &dest); - size_t Convert(const char* source, size_t sourceSize, char* dest, size_t destSize); - /// Bare wrapper around iconv; see iconv documention for details - size_t Convert(const char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft); - - /// @brief Get the required buffer size required to fit the source string in the target charset - /// @param source A string in the source charset - /// @param sourceSize Length of the source in bytes - /// @return Bytes required, including NUL terminator if applicable - size_t RequiredBufferSize(const char* source, size_t sourceSize); - /// @brief Get the required buffer size required to fit the source string in the target charset - /// @param str A string in the source charset - /// @return Bytes required, not including space needed for NUL terminator - size_t RequiredBufferSize(std::string const& str); - - /// Encoding-aware strlen for strings encoding in the source charset - size_t SrcStrLen(const char* str); - /// Encoding-aware strlen for strings encoding in the destination charset - size_t DstStrLen(const char* str); + void Convert(std::string_view source, std::string &dest); + /// @brief Convert a string from the source to destination charset + /// @param source String to convert + /// @param[out] dest Buffer to place the result in + /// @return Number of bytes written to dest + size_t Convert(std::string_view source, std::span dest); }; /// Is the conversion from src to dst supported by the linked iconv library? @@ -127,5 +110,4 @@ T const& GetEncodingsList() { return name_list; } - } -} +} // namespace agi::charset diff --git a/libaegisub/include/libaegisub/color.h b/libaegisub/include/libaegisub/color.h index b61b39f209..193976a5ae 100644 --- a/libaegisub/include/libaegisub/color.h +++ b/libaegisub/include/libaegisub/color.h @@ -15,6 +15,7 @@ #pragma once #include +#include namespace agi { struct Color { @@ -25,10 +26,9 @@ namespace agi { Color() = default; Color(unsigned char r, unsigned char g, unsigned char b, unsigned char a = 0); - Color(std::string const& str); + Color(std::string_view str); - bool operator==(Color const& col) const; - bool operator!=(Color const& col) const; + bool operator==(Color const&) const noexcept = default; std::string GetAssStyleFormatted() const; std::string GetAssOverrideFormatted() const; diff --git a/libaegisub/include/libaegisub/dispatch.h b/libaegisub/include/libaegisub/dispatch.h index d8be32fe3d..6d9b2d2c35 100644 --- a/libaegisub/include/libaegisub/dispatch.h +++ b/libaegisub/include/libaegisub/dispatch.h @@ -17,34 +17,32 @@ #include #include -namespace agi { - namespace dispatch { - typedef std::function Thunk; +namespace agi::dispatch { +using Thunk = std::function; - class Queue { - virtual void DoInvoke(Thunk thunk)=0; - public: - virtual ~Queue() { } +class Queue { + virtual void DoInvoke(Thunk&& thunk)=0; +public: + virtual ~Queue() = default; - /// Invoke the thunk on this processing queue, returning immediately - void Async(Thunk thunk); + /// Invoke the thunk on this processing queue, returning immediately + void Async(Thunk&& thunk); - /// Invoke the thunk on this processing queue, returning only when - /// it's complete - void Sync(Thunk thunk); - }; + /// Invoke the thunk on this processing queue, returning only when + /// it's complete + void Sync(Thunk&& thunk); +}; - /// Initialize the dispatch thread pools - /// @param invoke_main A function which invokes the thunk on the GUI thread - void Init(std::function invoke_main); +/// Initialize the dispatch thread pools +/// @param invoke_main A function which invokes the thunk on the GUI thread +void Init(std::function&& invoke_main); - /// Get the main queue, which runs on the GUI thread - Queue& Main(); +/// Get the main queue, which runs on the GUI thread +Queue& Main(); - /// Get the generic background queue, which runs thunks in parallel - Queue& Background(); +/// Get the generic background queue, which runs thunks in parallel +Queue& Background(); - /// Create a new serial queue - std::unique_ptr Create(); - } +/// Create a new serial queue +std::unique_ptr Create(); } diff --git a/libaegisub/include/libaegisub/exception.h b/libaegisub/include/libaegisub/exception.h index 831e4a3713..74c3ca4555 100644 --- a/libaegisub/include/libaegisub/exception.h +++ b/libaegisub/include/libaegisub/exception.h @@ -90,7 +90,7 @@ namespace agi { /// /// Deriving classes should always use this constructor for initialising /// the base class. - Exception(std::string msg) : message(std::move(msg)) { } + Exception(std::string&& msg) : message(std::move(msg)) { } public: /// @brief Get the outer exception error message diff --git a/libaegisub/include/libaegisub/file_mapping.h b/libaegisub/include/libaegisub/file_mapping.h index dde241f382..b525df1d25 100644 --- a/libaegisub/include/libaegisub/file_mapping.h +++ b/libaegisub/include/libaegisub/file_mapping.h @@ -14,10 +14,9 @@ // // Aegisub Project http://www.aegisub.org/ -#include - #include #include +#include namespace agi { // boost::interprocess::file_mapping is awesome and uses CreateFileA on Windows @@ -25,7 +24,7 @@ namespace agi { boost::interprocess::file_handle_t handle; public: - file_mapping(fs::path const& filename, bool temporary); + file_mapping(std::filesystem::path const& filename, bool temporary); ~file_mapping(); boost::interprocess::mapping_handle_t get_mapping_handle() const { return boost::interprocess::ipcdetail::mapping_handle_from_file_handle(handle); @@ -39,7 +38,7 @@ namespace agi { uint64_t file_size = 0; public: - read_file_mapping(fs::path const& filename); + read_file_mapping(std::filesystem::path const& filename); ~read_file_mapping(); uint64_t size() const { return file_size; } @@ -57,7 +56,7 @@ namespace agi { uint64_t write_mapping_start = 0; public: - temp_file_mapping(fs::path const& filename, uint64_t size); + temp_file_mapping(std::filesystem::path const& filename, uint64_t size); ~temp_file_mapping(); const char *read(int64_t offset, uint64_t length); diff --git a/libaegisub/include/libaegisub/format.h b/libaegisub/include/libaegisub/format.h index b1ca504786..2b750d4b26 100644 --- a/libaegisub/include/libaegisub/format.h +++ b/libaegisub/include/libaegisub/format.h @@ -14,10 +14,9 @@ // // Aegisub Project http://www.aegisub.org/ -#include - #include #include +#include #include class wxString; @@ -51,24 +50,24 @@ Out runtime_cast(In const& value) { template struct writer { - static void write(std::basic_ostream& out, int, T const& value) { + static void write(std::basic_ostream& out, size_t, T const& value) { out << value; } }; template struct writer { - static void write(std::basic_ostream& out, int max_len, const Char *value); + static void write(std::basic_ostream& out, size_t max_len, const Char *value); }; template struct writer> { - static void write(std::basic_ostream& out, int max_len, std::basic_string const& value); + static void write(std::basic_ostream& out, size_t max_len, std::basic_string const& value); }; // Ensure things with specializations don't get implicitly initialized -template<> struct writer; -template<> struct writer; +template<> struct writer; +template<> struct writer; template<> struct writer; template<> struct writer; diff --git a/libaegisub/include/libaegisub/format_flyweight.h b/libaegisub/include/libaegisub/format_flyweight.h index aa07061272..484365b825 100644 --- a/libaegisub/include/libaegisub/format_flyweight.h +++ b/libaegisub/include/libaegisub/format_flyweight.h @@ -14,8 +14,6 @@ // // Aegisub Project http://www.aegisub.org/ -#include - #include namespace agi { diff --git a/libaegisub/include/libaegisub/format_path.h b/libaegisub/include/libaegisub/format_path.h index ff195ece6d..4440a8c9e8 100644 --- a/libaegisub/include/libaegisub/format_path.h +++ b/libaegisub/include/libaegisub/format_path.h @@ -14,22 +14,20 @@ // // Aegisub Project http://www.aegisub.org/ -#include - -#include +#include namespace agi { // Default version quotes the path template<> -struct writer { - static void write(std::basic_ostream& out, int max_len, agi::fs::path const& value) { +struct writer { + static void write(std::basic_ostream& out, int max_len, std::filesystem::path const& value) { out << value.string(); } }; template<> -struct writer { - static void write(std::basic_ostream& out, int max_len, agi::fs::path const& value) { +struct writer { + static void write(std::basic_ostream& out, int max_len, std::filesystem::path const& value) { out << value.wstring(); } }; diff --git a/libaegisub/include/libaegisub/fs.h b/libaegisub/include/libaegisub/fs.h index 6b3e212ceb..f2dad7ca18 100644 --- a/libaegisub/include/libaegisub/fs.h +++ b/libaegisub/include/libaegisub/fs.h @@ -15,163 +15,160 @@ // Aegisub Project http://www.aegisub.org/ #include -#include -#include #include #include +#include #include #include #include #undef CreateDirectory -namespace agi { - namespace fs { - /// Define a filesystem error which takes a path or a string +namespace agi::fs { +using path = std::filesystem::path; + +/// Define a filesystem error which takes a path or a string #define DEFINE_FS_EXCEPTION(type, base, message) \ - struct type : public base { \ - type(path const& p) : base(message + p.string()) { } \ - type(std::string const& s) : base(s) { } \ - const char *GetName() const { return ""; } \ - Exception *Copy() const { return new type(*this); } \ - } - - /// @class agi::FileSystemError - /// @extends agi::Exception - /// @brief Base class for errors related to the file system - /// - /// This base class can not be instantiated. - /// File system errors do not support inner exceptions, as they - /// are always originating causes for errors. - DEFINE_EXCEPTION(FileSystemError, Exception); - - /// A file can't be accessed for some reason - DEFINE_FS_EXCEPTION(FileNotAccessible, FileSystemError, "File is not accessible: "); - - /// A file can't be accessed because there's no file by the given name - DEFINE_FS_EXCEPTION(FileNotFound, FileNotAccessible, "File not found: "); - - /// An error of some unknown type has occured - DEFINE_EXCEPTION(FileSystemUnknownError, FileSystemError); - - /// The path exists, but isn't a file - DEFINE_FS_EXCEPTION(NotAFile, FileNotAccessible, "Path is not a file (and should be): "); - - /// The path exists, but isn't a directory - DEFINE_FS_EXCEPTION(NotADirectory, FileNotAccessible, "Path is not a directory (and should be): "); - - /// The given path is too long for the filesystem - DEFINE_FS_EXCEPTION(PathTooLog, FileSystemError, "Path is too long: "); - - /// Insufficient free space to complete operation - DEFINE_FS_EXCEPTION(DriveFull, FileSystemError, "Insufficient free space to write file: "); - - /// Base class for access denied errors - DEFINE_FS_EXCEPTION(AccessDenied, FileNotAccessible, "Access denied to path: "); - - /// Trying to read the file gave an access denied error - DEFINE_FS_EXCEPTION(ReadDenied, AccessDenied, "Access denied when trying to read: "); - - /// Trying to write the file gave an access denied error - DEFINE_FS_EXCEPTION(WriteDenied, AccessDenied, "Access denied when trying to write: "); - - /// File exists and cannot be overwritten due to being read-only - DEFINE_FS_EXCEPTION(ReadOnlyFile, WriteDenied, "File is read-only: "); - - bool Exists(path const& p); - bool FileExists(path const& file); - bool DirectoryExists(path const& dir); - - /// Get the local-charset encoded shortname for a file - /// - /// This is purely for compatibility with external libraries which do - /// not support unicode filenames on Windows. On all other platforms, - /// it is a no-op. - std::string ShortName(path const& file_path); - - /// Check for amount of free space on a path - uintmax_t FreeSpace(path const& dir_path); - - /// Get the size in bytes of the file at path - /// - /// @throws agi::FileNotFound if path does not exist - /// @throws agi::acs::NotAFile if path is a directory - /// @throws agi::acs::Read if path exists but could not be read - uintmax_t Size(path const& file_path); - - /// Get the modification time of the file at path - /// - /// @throws agi::FileNotFound if path does not exist - /// @throws agi::acs::NotAFile if path is a directory - /// @throws agi::acs::Read if path exists but could not be read - time_t ModifiedTime(path const& file_path); - - /// Create a directory and all required intermediate directories - /// @throws agi::acs::Write if the directory could not be created. - /// - /// Trying to create a directory which already exists is not an error. - bool CreateDirectory(path const& dir_path); - - /// Touch the given path - /// - /// Creates the file if it does not exist, or updates the modified - /// time if it does - void Touch(path const& file_path); - - /// Rename a file or directory - /// @param from Source path - /// @param to Destination path - void Rename(path const& from, path const& to); - - /// Copy a file - /// @param from Source path - /// @param to Destination path - /// - /// The destination path will be created if it does not exist. - void Copy(path const& from, path const& to); - - /// Delete a file - /// @param path Path to file to delete - /// @throws agi::FileNotAccessibleError if file exists but could not be deleted - bool Remove(path const& file); - - /// Check if the file has the given extension - /// @param p Path to check - /// @param ext Case-insensitive extension, without leading dot - bool HasExtension(path const& p, std::string const& ext); - - agi::fs::path Canonicalize(agi::fs::path const& path); - - class DirectoryIterator { - struct PrivData; - std::shared_ptr privdata; - std::string value; - public: - typedef path value_type; - typedef path* pointer; - typedef path& reference; - typedef size_t difference_type; - typedef std::forward_iterator_tag iterator_category; - - bool operator==(DirectoryIterator const&) const; - bool operator!=(DirectoryIterator const& rhs) const { return !(*this == rhs); } - DirectoryIterator& operator++(); - std::string const& operator*() const { return value; } - - DirectoryIterator(path const& p, std::string const& filter); - DirectoryIterator(); - ~DirectoryIterator(); - - template void GetAll(T& cont); - }; - - static inline DirectoryIterator& begin(DirectoryIterator &it) { return it; } - static inline DirectoryIterator end(DirectoryIterator &) { return DirectoryIterator(); } - - template - inline void DirectoryIterator::GetAll(T& cont) { - copy(*this, end(*this), std::back_inserter(cont)); - } + struct type : public base { \ + type(path const& p) : base(message + p.string()) { } \ + type(std::string const& s) : base(s) { } \ + const char *GetName() const { return ""; } \ } + +/// @class agi::FileSystemError +/// @extends agi::Exception +/// @brief Base class for errors related to the file system +/// +/// This base class can not be instantiated. +/// File system errors do not support inner exceptions, as they +/// are always originating causes for errors. +DEFINE_EXCEPTION(FileSystemError, Exception); + +/// A file can't be accessed for some reason +DEFINE_FS_EXCEPTION(FileNotAccessible, FileSystemError, "File is not accessible: "); + +/// A file can't be accessed because there's no file by the given name +DEFINE_FS_EXCEPTION(FileNotFound, FileNotAccessible, "File not found: "); + +/// An error of some unknown type has occured +DEFINE_EXCEPTION(FileSystemUnknownError, FileSystemError); + +/// The path exists, but isn't a file +DEFINE_FS_EXCEPTION(NotAFile, FileNotAccessible, "Path is not a file (and should be): "); + +/// The path exists, but isn't a directory +DEFINE_FS_EXCEPTION(NotADirectory, FileNotAccessible, "Path is not a directory (and should be): "); + +/// The given path is too long for the filesystem +DEFINE_FS_EXCEPTION(PathTooLog, FileSystemError, "Path is too long: "); + +/// Insufficient free space to complete operation +DEFINE_FS_EXCEPTION(DriveFull, FileSystemError, "Insufficient free space to write file: "); + +/// Base class for access denied errors +DEFINE_FS_EXCEPTION(AccessDenied, FileNotAccessible, "Access denied to path: "); + +/// Trying to read the file gave an access denied error +DEFINE_FS_EXCEPTION(ReadDenied, AccessDenied, "Access denied when trying to read: "); + +/// Trying to write the file gave an access denied error +DEFINE_FS_EXCEPTION(WriteDenied, AccessDenied, "Access denied when trying to write: "); + +/// File exists and cannot be overwritten due to being read-only +DEFINE_FS_EXCEPTION(ReadOnlyFile, WriteDenied, "File is read-only: "); + +bool Exists(path const& p); +bool FileExists(path const& file); +bool DirectoryExists(path const& dir); + +/// Get the local-charset encoded shortname for a file +/// +/// This is purely for compatibility with external libraries which do +/// not support unicode filenames on Windows. On all other platforms, +/// it is a no-op. +std::string ShortName(path const& file_path); + +/// Check for amount of free space on a path +uintmax_t FreeSpace(path const& dir_path); + +/// Get the size in bytes of the file at path +/// +/// @throws agi::FileNotFound if path does not exist +/// @throws agi::acs::NotAFile if path is a directory +/// @throws agi::acs::Read if path exists but could not be read +uintmax_t Size(path const& file_path); + +/// Get the modification time of the file at path +/// +/// @throws agi::FileNotFound if path does not exist +/// @throws agi::acs::NotAFile if path is a directory +/// @throws agi::acs::Read if path exists but could not be read +std::filesystem::file_time_type ModifiedTime(path const& file_path); + +/// Create a directory and all required intermediate directories +/// @throws agi::acs::Write if the directory could not be created. +/// +/// Trying to create a directory which already exists is not an error. +bool CreateDirectory(path const& dir_path); + +/// Touch the given path +/// +/// Creates the file if it does not exist, or updates the modified +/// time if it does +void Touch(path const& file_path); + +/// Rename a file or directory +/// @param from Source path +/// @param to Destination path +void Rename(path const& from, path const& to); + +/// Copy a file +/// @param from Source path +/// @param to Destination path +/// +/// The destination path will be created if it does not exist. +void Copy(path const& from, path const& to); + +/// Delete a file +/// @param path Path to file to delete +/// @throws agi::FileNotAccessibleError if file exists but could not be deleted +bool Remove(path const& file); + +/// Check if the file has the given extension +/// @param p Path to check +/// @param ext Case-insensitive extension, without leading dot +bool HasExtension(path const& p, std::string const& ext); + +std::filesystem::path Canonicalize(std::filesystem::path const& path); + +class DirectoryIterator { + struct PrivData; + std::shared_ptr privdata; + std::string value; +public: + typedef path value_type; + typedef path* pointer; + typedef path& reference; + typedef size_t difference_type; + typedef std::forward_iterator_tag iterator_category; + + bool operator==(DirectoryIterator const&) const; + DirectoryIterator& operator++(); + std::string const& operator*() const { return value; } + + DirectoryIterator(path const& p, std::string const& filter); + DirectoryIterator(); + ~DirectoryIterator(); + + template void GetAll(T& cont); +}; + +static inline DirectoryIterator& begin(DirectoryIterator &it) { return it; } +static inline DirectoryIterator end(DirectoryIterator &) { return DirectoryIterator(); } + +template +inline void DirectoryIterator::GetAll(T& cont) { + copy(*this, end(*this), std::back_inserter(cont)); } +} // namespace agi::fs diff --git a/libaegisub/include/libaegisub/fs_fwd.h b/libaegisub/include/libaegisub/fs_fwd.h deleted file mode 100644 index c2d8869648..0000000000 --- a/libaegisub/include/libaegisub/fs_fwd.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2013, Thomas Goyne -// -// Permission to use, copy, modify, and 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. -// -// Aegisub Project http://www.aegisub.org/ - -namespace boost { namespace filesystem { class path; } } -namespace agi { namespace fs { typedef boost::filesystem::path path; } } diff --git a/libaegisub/include/libaegisub/hotkey.h b/libaegisub/include/libaegisub/hotkey.h index 81cae91fb4..59180f4be4 100644 --- a/libaegisub/include/libaegisub/hotkey.h +++ b/libaegisub/include/libaegisub/hotkey.h @@ -12,22 +12,20 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -#include +#include #include #include +#include #include -#include #include namespace json { class UnknownElement; - typedef std::map Object; + typedef std::map> Object; } -namespace agi { - namespace hotkey { - +namespace agi::hotkey { /// @class Combo /// A Combo represents a linear sequence of characters set in an std::vector. /// This makes up a single combination, or "Hotkey". @@ -39,22 +37,22 @@ class Combo { /// Constructor /// @param ctx Context /// @param cmd Command name - Combo(std::string ctx, std::string cmd, std::string keys) - : keys(std::move(keys)) - , cmd_name(std::move(cmd)) - , context(std::move(ctx)) + Combo(std::string_view ctx, std::string_view cmd, std::string_view keys) + : keys(keys) + , cmd_name(cmd) + , context(ctx) { } /// String representation of the Combo - std::string const& Str() const { return keys; } + std::string_view Str() const { return keys; } /// Command name triggered by the combination. /// @return Command name - std::string const& CmdName() const { return cmd_name; } + std::string_view CmdName() const { return cmd_name; } /// Context this Combo is triggered in. - std::string const& Context() const { return context; } + std::string_view Context() const { return context; } }; /// @class Hotkey @@ -62,17 +60,17 @@ class Combo { class Hotkey { public: /// Map to hold Combo instances - typedef std::multimap HotkeyMap; + typedef std::multimap> HotkeyMap; private: HotkeyMap cmd_map; ///< Command name -> Combo std::vector str_map; ///< Sorted by string representation - const agi::fs::path config_file; ///< Default user config location. + const std::filesystem::path config_file; ///< Default user config location. bool backup_config_file = false; /// Build hotkey map. /// @param context Context being parsed. /// @param object json::Object holding items for context being parsed. - void BuildHotkey(std::string const& context, const json::Object& object); + void BuildHotkey(std::string_view context, const json::Object& object); /// Write active Hotkey configuration to disk. void Flush(); @@ -85,32 +83,28 @@ class Hotkey { /// Constructor /// @param file Location of user config file. /// @param default_config Default config. - Hotkey(agi::fs::path const& file, std::pair default_config); - - template - Hotkey(agi::fs::path const& file, const char (&default_config)[N]) - : Hotkey(file, {default_config, N - 1}) { } + Hotkey(std::filesystem::path const& file, std::string_view default_config); /// Scan for a matching key. /// @param context Context requested. /// @param str Hyphen separated key sequence. /// @param always Enable the "Always" override context /// @return Name of command or "" if none match - std::string Scan(const std::string &context, const std::string &str, bool always) const; + std::string_view Scan(std::string_view context, std::string_view str, bool always) const; - bool HasHotkey(const std::string &context, const std::string &str) const; + bool HasHotkey(std::string_view context, std::string_view str) const; /// Get the string representation of the hotkeys for the given command /// @param context Context requested /// @param command Command name /// @return A vector of all hotkeys for that command in the context - std::vector GetHotkeys(const std::string &context, const std::string &command) const; + std::vector GetHotkeys(std::string_view context, std::string_view command) const; /// Get a string representation of a hotkeys for the given command /// @param context Context requested /// @param command Command name /// @return A hotkey for the given command or "" if there are none - std::string GetHotkey(const std::string &context, const std::string &command) const; + std::string_view GetHotkey(std::string_view context, std::string_view command) const; /// Get the raw command name -> combo map for all registered hotkeys HotkeyMap const& GetHotkeyMap() const { return cmd_map; } @@ -121,5 +115,4 @@ class Hotkey { DEFINE_SIGNAL_ADDERS(HotkeysChanged, AddHotkeyChangeListener) }; - } // namespace hotkey -} // namespace agi +} // namespace agi::hotkey diff --git a/libaegisub/include/libaegisub/io.h b/libaegisub/include/libaegisub/io.h index 5ab4f4af3f..8ac1ed3543 100644 --- a/libaegisub/include/libaegisub/io.h +++ b/libaegisub/include/libaegisub/io.h @@ -12,35 +12,28 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -/// @file io.h -/// @brief Public interface for IO methods. -/// @ingroup libaegisub - #include -#include -#include +#include #include #include -namespace agi { - namespace io { +namespace agi::io { DEFINE_EXCEPTION(IOError, Exception); DEFINE_EXCEPTION(IOFatal, IOError); -std::unique_ptr Open(fs::path const& file, bool binary = false); +std::unique_ptr Open(std::filesystem::path const& file, bool binary = false); class Save { std::unique_ptr fp; - const fs::path file_name; - const fs::path tmp_name; + const std::filesystem::path file_name; + const std::filesystem::path tmp_name; public: - Save(fs::path const& file, bool binary = false); + Save(std::filesystem::path const& file, bool binary = false); ~Save() noexcept(false); std::ostream& Get() { return *fp; } }; - } // namespace io -} // namespace agi +} // namespace agi::io diff --git a/libaegisub/include/libaegisub/json.h b/libaegisub/include/libaegisub/json.h index 4862cd0c15..1bb981af01 100644 --- a/libaegisub/include/libaegisub/json.h +++ b/libaegisub/include/libaegisub/json.h @@ -12,14 +12,11 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -/// @file json.h -/// @brief Parse JSON files and return json::UnknownElement -/// @ingroup libaegisub io - #include -#include -namespace agi { namespace json_util { +#include + +namespace agi::json_util { /// Parse a JSON stream. /// @param stream JSON stream to parse @@ -30,6 +27,6 @@ json::UnknownElement parse(std::istream &stream); /// @param file Path to JSON file. /// @param Default config file to load incase of nonexistent file /// @return json::UnknownElement -json::UnknownElement file(agi::fs::path const& file, std::pair default_config); +json::UnknownElement file(std::filesystem::path const& file, std::string_view default_config); -} } +} diff --git a/libaegisub/include/libaegisub/kana_table.h b/libaegisub/include/libaegisub/kana_table.h index 1a25c098f4..bf9f05eb4a 100644 --- a/libaegisub/include/libaegisub/kana_table.h +++ b/libaegisub/include/libaegisub/kana_table.h @@ -20,12 +20,12 @@ namespace agi { struct kana_pair { - const char *kana; - const char *romaji; + std::string_view kana; + std::string_view romaji; }; /// Transliterated romaji for the given kana, or nullptr if not applicable - std::vector kana_to_romaji(std::string const& kana); + std::vector kana_to_romaji(std::string_view kana); - boost::iterator_range romaji_to_kana(std::string const& romaji); + boost::iterator_range romaji_to_kana(std::string_view romaji); } diff --git a/libaegisub/include/libaegisub/karaoke_matcher.h b/libaegisub/include/libaegisub/karaoke_matcher.h index 527cac18f5..dc5abf5dca 100644 --- a/libaegisub/include/libaegisub/karaoke_matcher.h +++ b/libaegisub/include/libaegisub/karaoke_matcher.h @@ -1,4 +1,4 @@ -// Copyright (c) 2013, Thomas Goyne +// Copyright (c) 2022, Thomas Goyne // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -14,17 +14,69 @@ // // Aegisub Project http://www.aegisub.org/ +#include +#include + +#include #include +#include #include namespace agi { - struct karaoke_match_result { - /// The number of strings in the source matched - size_t source_length; - /// The number of characters in the destination string matched - size_t destination_length; + +struct KaraokeMatchResult { + /// The number of strings in the source matched + size_t source_length; + /// The number of characters in the destination string matched + size_t destination_length; + + bool operator==(KaraokeMatchResult const&) const = default; +}; + +/// Try to automatically select the portion of dst which corresponds to the first string in src +KaraokeMatchResult AutoMatchKaraoke(std::vector const& src, std::string_view dst); + +class KaraokeMatcher { +public: + struct MatchGroup { + std::span src; + std::string_view dst; }; - /// Try to automatically select the portion of dst which corresponds to the first string in src - karaoke_match_result auto_match_karaoke(std::vector const& src, std::string const& dst); -} +private: + std::vector syllables; + std::string destination_str; + + std::vector matched_groups; + + std::vector character_positions; + size_t src_start = 0, dst_start = 0; + size_t src_len = 0, dst_len = 0; + + agi::BreakIterator bi; + +public: + /// Start processing a new line pair + void SetInputData(std::vector&& src, std::string&& dst); + /// Build and return the output line from the matched syllables + std::string GetOutputLine() const; + + std::span MatchedGroups() const { return matched_groups; } + std::span CurrentSourceSelection() const; + std::span UnmatchedSource() const; + std::string_view CurrentDestinationSelection() const; + std::string_view UnmatchedDestination() const; + + // Adjust source and destination match lengths + bool IncreaseSourceMatch(); + bool DecreaseSourceMatch(); + bool IncreaseDestinationMatch(); + bool DecreaseDestinationMatch(); + /// Attempt to treat source as Japanese romaji, destination as Japanese kana+kanji, and make an automatic match + void AutoMatchJapanese(); + /// Accept current selection and save match + bool AcceptMatch(); + /// Undo last match, adding it back to the unmatched input + bool UndoMatch(); +}; +} // namespace agi diff --git a/libaegisub/include/libaegisub/keyframe.h b/libaegisub/include/libaegisub/keyframe.h index 6fc82ff49d..91ac96fd48 100644 --- a/libaegisub/include/libaegisub/keyframe.h +++ b/libaegisub/include/libaegisub/keyframe.h @@ -13,23 +13,21 @@ // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include -#include +#include #include -namespace agi { - namespace keyframe { - /// @brief Load a keyframe file - /// @param filename File to load - /// @return List of frame numbers which are keyframes - std::vector Load(agi::fs::path const& filename); +namespace agi::keyframe { +/// @brief Load a keyframe file +/// @param filename File to load +/// @return List of frame numbers which are keyframes +std::vector Load(std::filesystem::path const& filename); - /// @brief Save keyframes to a file - /// @param filename File to save to - /// @param keyframes List of keyframes to save - void Save(agi::fs::path const& filename, std::vector const& keyframes); +/// @brief Save keyframes to a file +/// @param filename File to save to +/// @param keyframes List of keyframes to save +void Save(std::filesystem::path const& filename, std::vector const& keyframes); - DEFINE_EXCEPTION(KeyframeFormatParseError, agi::InvalidInputException); - DEFINE_EXCEPTION(UnknownKeyframeFormatError, agi::InvalidInputException); - } +DEFINE_EXCEPTION(KeyframeFormatParseError, agi::InvalidInputException); +DEFINE_EXCEPTION(UnknownKeyframeFormatError, agi::InvalidInputException); } diff --git a/libaegisub/include/libaegisub/line_iterator.h b/libaegisub/include/libaegisub/line_iterator.h index 1e4fee9b15..23a288ea34 100644 --- a/libaegisub/include/libaegisub/line_iterator.h +++ b/libaegisub/include/libaegisub/line_iterator.h @@ -12,16 +12,13 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -/// @file line_iterator.h -/// @brief An iterator over lines in a stream -/// @ingroup libaegisub - #pragma once +#include +#include #include #include -#include #include namespace agi { @@ -31,15 +28,14 @@ namespace charset { class IconvWrapper; } class line_iterator_base { std::istream *stream = nullptr; ///< Stream to iterate over std::shared_ptr conv; - int cr = '\r'; ///< CR character in the source encoding - int lf = '\n'; ///< LF character in the source encoding + std::array cr{'\r', 0, 0, 0}, lf{'\n', 0, 0, 0}; size_t width = 1; ///< width of LF character in the source encoding protected: bool getline(std::string &str); public: - line_iterator_base(std::istream &stream, std::string encoding = "utf-8"); + line_iterator_base(std::istream &stream, const char *encoding = "utf-8"); line_iterator_base() = default; line_iterator_base(line_iterator_base const&) = default; @@ -49,13 +45,12 @@ class line_iterator_base { line_iterator_base& operator=(line_iterator_base&&) = default; bool operator==(line_iterator_base const& rgt) const { return stream == rgt.stream; } - bool operator!=(line_iterator_base const& rgt) const { return !operator==(rgt); } }; /// @class line_iterator /// @brief An iterator over lines in a stream template -class line_iterator final : public line_iterator_base, public std::iterator { +class line_iterator final : public line_iterator_base { OutputType value; ///< Value to return when this is dereference /// @brief Convert a string to the output type @@ -69,13 +64,20 @@ class line_iterator final : public line_iterator_base, public std::iterator - #include #include +#include #include #include @@ -113,7 +112,7 @@ class JsonEmitter final : public Emitter { public: /// Constructor /// @param directory Directory to write the log file in - JsonEmitter(fs::path const& directory); + JsonEmitter(std::filesystem::path const& directory); void log(SinkMessage const&) override; }; diff --git a/libaegisub/include/libaegisub/lua/ffi.h b/libaegisub/include/libaegisub/lua/ffi.h index 848cf08058..17cf0088d7 100644 --- a/libaegisub/include/libaegisub/lua/ffi.h +++ b/libaegisub/include/libaegisub/lua/ffi.h @@ -19,7 +19,7 @@ #include #include -namespace agi { namespace lua { +namespace agi::lua { void do_register_lib_function(lua_State *L, const char *name, const char *type_name, void *func); void do_register_lib_table(lua_State *L, std::initializer_list types); @@ -53,4 +53,4 @@ char *strndup(T const& str) { return ret; } -} } +} // namespace agi::lua diff --git a/libaegisub/include/libaegisub/lua/modules.h b/libaegisub/include/libaegisub/lua/modules.h index 9783b9723b..ee103ee893 100644 --- a/libaegisub/include/libaegisub/lua/modules.h +++ b/libaegisub/include/libaegisub/lua/modules.h @@ -16,6 +16,6 @@ struct lua_State; -namespace agi { namespace lua { +namespace agi::lua { void preload_modules(lua_State *L); -} } +} diff --git a/libaegisub/include/libaegisub/lua/script_reader.h b/libaegisub/include/libaegisub/lua/script_reader.h index 9c9de1300f..655ce1114f 100644 --- a/libaegisub/include/libaegisub/lua/script_reader.h +++ b/libaegisub/include/libaegisub/lua/script_reader.h @@ -14,16 +14,15 @@ // // Aegisub Project http://www.aegisub.org/ -#include - +#include #include struct lua_State; -namespace agi { namespace lua { +namespace agi::lua { /// Load a Lua or Moonscript file at the given path - bool LoadFile(lua_State *L, agi::fs::path const& filename); + bool LoadFile(lua_State *L, std::filesystem::path const& filename); /// Install our module loader and add include_path to the module search /// path of the given lua state - bool Install(lua_State *L, std::vector const& include_path); -} } + bool Install(lua_State *L, std::vector const& include_path); +} diff --git a/libaegisub/include/libaegisub/lua/utils.h b/libaegisub/include/libaegisub/lua/utils.h index c5a65d6e44..a023b30edd 100644 --- a/libaegisub/include/libaegisub/lua/utils.h +++ b/libaegisub/include/libaegisub/lua/utils.h @@ -23,20 +23,15 @@ #include -#ifndef BOOST_NORETURN -#include -#define BOOST_NORETURN BOOST_ATTRIBUTE_NORETURN -#endif - -namespace agi { namespace lua { +namespace agi::lua { // Exception type for errors where the error details are on the lua stack struct error_tag {}; // Below are functionally equivalent to the luaL_ functions, but using a C++ // exception for stack unwinding -int BOOST_NORETURN error(lua_State *L, const char *fmt, ...); -int BOOST_NORETURN argerror(lua_State *L, int narg, const char *extramsg); -int BOOST_NORETURN typerror(lua_State *L, int narg, const char *tname); +[[noreturn]] int error(lua_State *L, const char *fmt, ...); +[[noreturn]] int argerror(lua_State *L, int narg, const char *extramsg); +[[noreturn]] int typerror(lua_State *L, int narg, const char *tname); void argcheck(lua_State *L, bool cond, int narg, const char *msg); inline void push_value(lua_State *L, bool value) { lua_pushboolean(L, value); } @@ -102,7 +97,7 @@ std::string get_string(lua_State *L, int idx); std::string get_global_string(lua_State *L, const char *name); std::string check_string(lua_State *L, int idx); -int check_int(lua_State *L, int idx); +long check_int(lua_State *L, int idx); size_t check_uint(lua_State *L, int idx); void *check_udata(lua_State *L, int idx, const char *mt); @@ -165,4 +160,4 @@ void lua_for_each(lua_State *L, Func&& func) { /// moonscript line rewriting support int add_stack_trace(lua_State *L); -} } +} diff --git a/libaegisub/include/libaegisub/mru.h b/libaegisub/include/libaegisub/mru.h index 9ce3dd43f2..326576e16d 100644 --- a/libaegisub/include/libaegisub/mru.h +++ b/libaegisub/include/libaegisub/mru.h @@ -13,11 +13,11 @@ // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include -#include +#include +#include #include #include -#include namespace json { class UnknownElement; @@ -39,45 +39,41 @@ DEFINE_EXCEPTION(MRUError, Exception); class MRUManager { public: /// @brief Map for time->value pairs. - using MRUListMap = std::vector; + using MRUListMap = std::vector; /// @brief Constructor /// @param config File to load MRU values from - MRUManager(agi::fs::path const& config, std::pair default_config, agi::Options *options = nullptr); - - template - MRUManager(agi::fs::path const& file, const char (&default_config)[N]) - : MRUManager(file, {default_config, N - 1}) { } + MRUManager(std::filesystem::path const& config, std::string_view default_config, agi::Options *options = nullptr); /// @brief Add entry to the list. /// @param key List name /// @param entry Entry to add /// @exception MRUError thrown when an invalid key is used. - void Add(const char *key, agi::fs::path const& entry); + void Add(std::string_view key, std::filesystem::path const& entry); /// @brief Remove entry from the list. /// @param key List name /// @param entry Entry to add /// @exception MRUError thrown when an invalid key is used. - void Remove(const char *key, agi::fs::path const& entry); + void Remove(std::string_view key, std::filesystem::path const& entry); /// @brief Return list /// @param key List name /// @exception MRUError thrown when an invalid key is used. - const MRUListMap* Get(const char *key); + const MRUListMap* Get(std::string_view key); /// @brief Return A single entry in a list. /// @param key List name /// @param entry 0-base position of entry /// @exception MRUError thrown when an invalid key is used. - agi::fs::path const& GetEntry(const char *key, const size_t entry); + std::filesystem::path const& GetEntry(std::string_view key, const size_t entry); /// Write MRU lists to disk. void Flush(); private: /// Internal name of the config file, set during object construction. - const agi::fs::path config_name; + const std::filesystem::path config_name; /// User preferences object for maximum number of items to list agi::Options *const options; @@ -88,11 +84,11 @@ class MRUManager { /// @brief Load MRU Lists. /// @param key List name. /// @param array json::Array of values. - void Load(const char *key, ::json::Array const& array); + void Load(std::string_view key, ::json::Array const& array); /// @brief Prune MRUListMap to the desired length. /// This uses the user-set values for MRU list length. - void Prune(const char *key, MRUListMap& map) const; - MRUListMap &Find(const char *key); + void Prune(std::string_view key, MRUListMap& map) const; + MRUListMap &Find(std::string_view key); }; } // namespace agi diff --git a/libaegisub/include/libaegisub/option.h b/libaegisub/include/libaegisub/option.h index eb94ef670b..61e19fb3fc 100644 --- a/libaegisub/include/libaegisub/option.h +++ b/libaegisub/include/libaegisub/option.h @@ -12,23 +12,17 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -/// @file option.h -/// @brief Public interface for option values. -/// @ingroup libaegisub - #pragma once -#include +#include #include #include #include #include -#include - namespace json { class UnknownElement; - typedef std::map Object; + typedef std::map> Object; } namespace agi { @@ -46,7 +40,7 @@ class Options { std::vector> values; /// User config (file that will be written to disk) - const agi::fs::path config_file; + const std::filesystem::path config_file; /// Settings. const OptionSetting setting; @@ -60,11 +54,7 @@ class Options { /// @brief Constructor /// @param file User config that will be loaded from and written back to. /// @param default_config Default configuration. - Options(agi::fs::path const& file, std::pair default_config, const OptionSetting setting = NONE); - - template - Options(agi::fs::path const& file, const char (&default_config)[N], const OptionSetting setting = NONE) - : Options(file, {default_config, N - 1}, setting) { } + Options(std::filesystem::path const& file, std::string_view default_config, OptionSetting setting = NONE); /// Destructor ~Options(); @@ -72,8 +62,7 @@ class Options { /// @brief Get an option by name. /// @param name Option to get. /// Get an option value object by name throw an internal exception if the option is not found. - OptionValue *Get(const char *name); - OptionValue *Get(std::string const& name) { return Get(name.c_str()); } + OptionValue *Get(std::string_view name); /// @brief Next configuration file to load. /// @param[in] src Stream to load from. diff --git a/libaegisub/include/libaegisub/option_value.h b/libaegisub/include/libaegisub/option_value.h index 63870419cc..1090e0bd9f 100644 --- a/libaegisub/include/libaegisub/option_value.h +++ b/libaegisub/include/libaegisub/option_value.h @@ -68,7 +68,7 @@ class OptionValue { protected: void NotifyChanged() { ValueChanged(*this); } - OptionValue(std::string name) BOOST_NOEXCEPT : name(std::move(name)) { } + OptionValue(std::string name) noexcept : name(std::move(name)) { } public: virtual ~OptionValue() = default; @@ -130,16 +130,16 @@ CONFIG_OPTIONVALUE(Double, double) CONFIG_OPTIONVALUE(Color, Color) CONFIG_OPTIONVALUE(Bool, bool) -#define CONFIG_OPTIONVALUE_LIST(type_name, type) \ +#define CONFIG_OPTIONVALUE_LIST(type_name, type, rt) \ class OptionValueList##type_name final : public OptionValue { \ std::vector array; \ std::vector array_default; \ std::string name; \ public: \ - typedef std::vector value_type; \ - OptionValueList##type_name(std::string name, std::vector const& value = std::vector()) \ - : OptionValue(std::move(name)) \ - , array(value), array_default(value) { } \ + using value_type = std::vector; \ + using raw_type = rt; \ + OptionValueList##type_name(std::string name, std::vector const& value = {}) \ + : OptionValue(std::move(name)), array(value), array_default(value) { } \ std::vector const& GetValue() const { return array; } \ void SetValue(std::vector val) { array = std::move(val); NotifyChanged(); } \ OptionType GetType() const { return OptionType::List##type_name; } \ @@ -148,11 +148,11 @@ CONFIG_OPTIONVALUE(Bool, bool) void Set(const OptionValue *nv); \ }; -CONFIG_OPTIONVALUE_LIST(String, std::string) -CONFIG_OPTIONVALUE_LIST(Int, int64_t) -CONFIG_OPTIONVALUE_LIST(Double, double) -CONFIG_OPTIONVALUE_LIST(Color, Color) -CONFIG_OPTIONVALUE_LIST(Bool, bool) +CONFIG_OPTIONVALUE_LIST(String, std::string, std::string) +CONFIG_OPTIONVALUE_LIST(Int, int64_t, int64_t) +CONFIG_OPTIONVALUE_LIST(Double, double, double) +CONFIG_OPTIONVALUE_LIST(Color, Color, std::string) +CONFIG_OPTIONVALUE_LIST(Bool, bool, bool) #define CONFIG_OPTIONVALUE_ACCESSORS(ReturnType, Type) \ inline ReturnType const& OptionValue::Get##Type() const { return As(OptionType::Type)->GetValue(); } \ diff --git a/libaegisub/include/libaegisub/path.h b/libaegisub/include/libaegisub/path.h index a1bcb7afcf..ab17c2f184 100644 --- a/libaegisub/include/libaegisub/path.h +++ b/libaegisub/include/libaegisub/path.h @@ -14,12 +14,11 @@ // // Aegisub Project http://www.aegisub.org/ -#include - #include -#include +#include namespace agi { +namespace fs { using path = std::filesystem::path; } /// Class for handling everything path-related in Aegisub class Path { /// Token -> Path map @@ -35,22 +34,21 @@ class Path { /// Decode and normalize a path which may begin with a registered token /// @param path Path which is either already absolute or begins with a token /// @return Absolute path - fs::path Decode(std::string const& path) const; + fs::path Decode(std::string_view path) const; /// If path is relative, make it absolute relative to the token's path /// @param path A possibly relative path /// @param token Token containing base path for resolving relative paths /// @return Absolute path if `path` is absolute or `token` is set, `path` otherwise /// @throws InternalError if `token` is not a valid token name - fs::path MakeAbsolute(fs::path path, std::string const& token) const; + fs::path MakeAbsolute(fs::path path, std::string_view token) const; /// If `token` is set, make `path` relative to it /// @param path An absolute path /// @param token Token name to make `path` relative to /// @return A path relative to `token`'s value if `token` is set, `path` otherwise /// @throws InternalError if `token` is not a valid token name - fs::path MakeRelative(fs::path const& path, std::string const& token) const; - fs::path MakeRelative(fs::path const& path, const char *token) const { return MakeRelative(path, std::string(token)); } + fs::path MakeRelative(fs::path const& path, std::string_view token) const; /// Make `path` relative to `base`, if possible /// @param path An absolute path @@ -67,7 +65,7 @@ class Path { /// @param token_name A single word token beginning with '?' /// @param token_value An absolute path to a directory or file /// @throws InternalError if `token` is not a valid token name - void SetToken(const char *token_name, fs::path const& token_value); + void SetToken(std::string_view token_name, fs::path const& token_value); }; -} +} // namespace agi diff --git a/libaegisub/include/libaegisub/signal.h b/libaegisub/include/libaegisub/signal.h index 091cc78737..fd8ea936aa 100644 --- a/libaegisub/include/libaegisub/signal.h +++ b/libaegisub/include/libaegisub/signal.h @@ -19,7 +19,7 @@ #include #include -namespace agi { namespace signal { +namespace agi::signal { class Connection; /// Implementation details; nothing outside this file should directly touch @@ -63,10 +63,10 @@ class Connection { std::unique_ptr token; public: Connection() = default; - Connection(UnscopedConnection src) BOOST_NOEXCEPT : token(src.token) { token->claimed = true; } - Connection(Connection&& that) BOOST_NOEXCEPT : token(std::move(that.token)) { } - Connection(detail::ConnectionToken *token) BOOST_NOEXCEPT : token(token) { token->claimed = true; } - Connection& operator=(Connection&& that) BOOST_NOEXCEPT { token = std::move(that.token); return *this; } + Connection(UnscopedConnection src) noexcept : token(src.token) { token->claimed = true; } + Connection(Connection&& that) noexcept = default; + Connection(detail::ConnectionToken *token) noexcept : token(token) { token->claimed = true; } + Connection& operator=(Connection&& that) noexcept = default; /// @brief End this connection /// @@ -100,7 +100,7 @@ namespace detail { SignalBase& operator=(SignalBase const&) = delete; protected: SignalBase() = default; - virtual ~SignalBase() {}; + virtual ~SignalBase() = default; /// @brief Notify a slot that it has been disconnected /// @param tok Token to disconnect /// @@ -131,18 +131,13 @@ class Signal final : private detail::SignalBase { std::vector> slots; /// Signals currently connected to this slot void Disconnect(detail::ConnectionToken *tok) override { - for (auto it = begin(slots), e = end(slots); it != e; ++it) { - if (tok == it->first) { - slots.erase(it); - return; - } - } + std::erase_if(slots, [=](auto& slot) { return slot.first == tok; }); } - UnscopedConnection DoConnect(Slot sig) { - auto token = MakeToken(); - slots.emplace_back(token, sig); - return UnscopedConnection(token); + UnscopedConnection DoConnect(Slot&& sig) { + std::unique_ptr token(MakeToken()); + slots.emplace_back(token.get(), std::move(sig)); + return UnscopedConnection(token.release()); } public: @@ -167,8 +162,8 @@ class Signal final : private detail::SignalBase { /// @brief Connect a signal to this slot /// @param sig Signal to connect /// @return The connection object - UnscopedConnection Connect(Slot sig) { - return DoConnect(sig); + UnscopedConnection Connect(Slot&& sig) { + return DoConnect(std::move(sig)); } // Convenience wrapper for a member function which matches the signal's signature @@ -208,7 +203,7 @@ inline std::vector make_vector(std::initializer_list(std::begin(connections), std::end(connections)); } -} } +} /// @brief Define functions which forward their arguments to the connect method /// of the named signal diff --git a/libaegisub/include/libaegisub/spellchecker.h b/libaegisub/include/libaegisub/spellchecker.h index 450a39407c..8844774133 100644 --- a/libaegisub/include/libaegisub/spellchecker.h +++ b/libaegisub/include/libaegisub/spellchecker.h @@ -16,7 +16,7 @@ #pragma once -#include +#include #include namespace agi { @@ -26,31 +26,31 @@ class SpellChecker { /// Add word to the dictionary /// @param word Word to add - virtual void AddWord(std::string const& word)=0; + virtual void AddWord(std::string_view word)=0; /// Remove word from the dictionary /// @param word Word to remove - virtual void RemoveWord(std::string const& word)=0; + virtual void RemoveWord(std::string_view word)=0; /// Can the word be added to the current dictionary? /// @param word Word to check /// @return Whether or not word can be added - virtual bool CanAddWord(std::string const& word)=0; + virtual bool CanAddWord(std::string_view word)=0; /// Can the word be removed from the current dictionary? /// @param word Word to check /// @return Whether or not word can be removed - virtual bool CanRemoveWord(std::string const& word)=0; + virtual bool CanRemoveWord(std::string_view word)=0; /// Check if the given word is spelled correctly /// @param word Word to check /// @return Whether or not the word is valid - virtual bool CheckWord(std::string const& word)=0; + virtual bool CheckWord(std::string_view word)=0; /// Get possible corrections for a misspelled word /// @param word Word to get suggestions for /// @return List of suggestions, if any - virtual std::vector GetSuggestions(std::string const& word)=0; + virtual std::vector GetSuggestions(std::string_view word)=0; /// Get a list of languages which dictionaries are present for virtual std::vector GetLanguageList()=0; diff --git a/libaegisub/include/libaegisub/split.h b/libaegisub/include/libaegisub/split.h index 0b152b2245..48f951a770 100644 --- a/libaegisub/include/libaegisub/split.h +++ b/libaegisub/include/libaegisub/split.h @@ -14,63 +14,48 @@ // // Aegisub Project http://www.aegisub.org/ -#include +#include +#include namespace agi { - typedef boost::iterator_range StringRange; - - template + template class split_iterator { - bool is_end = false; - Iterator b; - Iterator cur; - Iterator e; - typename Iterator::value_type c; + std::basic_string_view str; + size_t pos = 0; + Char delim; public: using iterator_category = std::forward_iterator_tag; - using value_type = boost::iterator_range; + using value_type = std::string_view; using pointer = value_type*; using reference = value_type&; using difference_type = ptrdiff_t; - split_iterator(Iterator begin, Iterator end, typename Iterator::value_type c) - : b(begin), cur(begin), e(end), c(c) + split_iterator(std::basic_string_view str, Char c) + : str(str), delim(c) { - if (b != e) - cur = std::find(b, e, c); - else - is_end = true; + pos = str.find(delim); } - split_iterator() : is_end(true) { } + split_iterator() = default; - bool eof() const { return is_end; } + bool eof() const { return str.size() == 0; } - boost::iterator_range operator*() const { - return boost::make_iterator_range(b, cur); + std::basic_string_view operator*() const { + return str.substr(0, pos); } bool operator==(split_iterator const& it) const { - if (is_end || it.is_end) - return is_end && it.is_end; - return b == it.b && cur == it.cur && e == it.e && c == it.c; - } - - bool operator!=(split_iterator const& it) const { - return !(*this == it); + return str == it.str && (str.size() == 0 || delim == it.delim); } split_iterator& operator++() { - if (cur != e) { - b = cur + 1; - cur = std::find(b, e, c); + if (pos == str.npos) { + str = str.substr(str.size()); + } else { + str = str.substr(pos + 1); + pos = str.find(delim); } - else { - b = e; - is_end = true; - } - return *this; } @@ -81,29 +66,43 @@ namespace agi { } }; - template - split_iterator begin(split_iterator const& it) { + template + split_iterator begin(split_iterator const& it) { return it; } - template - split_iterator end(split_iterator const&) { - return split_iterator(); + template + split_iterator end(split_iterator const&) { + return split_iterator(); } - static inline std::string str(StringRange const& r) { - return std::string(r.begin(), r.end()); + template + split_iterator Split(std::basic_string_view str, Char delim) { + return split_iterator(str, delim); } - template - split_iterator Split(Str const& str, Char delim) { - return split_iterator(begin(str), end(str), delim); + inline split_iterator Split(std::basic_string_view str, char delim) { + return split_iterator(str, delim); } - template - void Split(Cont& out, Str const& str, Char delim) { + template + void Split(Cont& out, std::basic_string_view str, Char delim) { out.clear(); for (auto const& tok : Split(str, delim)) out.emplace_back(begin(tok), end(tok)); } + + template + void Split(Cont& out, std::basic_string_view str, char delim) { + Split(out, str, delim); + } + + inline std::string_view Trim(std::string_view str) { + std::locale loc; + while (str.size() && std::isspace(str.front(), loc)) + str.remove_prefix(1); + while (str.size() && std::isspace(str.back(), loc)) + str.remove_suffix(1); + return str; + } } diff --git a/libaegisub/include/libaegisub/string.h b/libaegisub/include/libaegisub/string.h new file mode 100644 index 0000000000..4af862610c --- /dev/null +++ b/libaegisub/include/libaegisub/string.h @@ -0,0 +1,61 @@ +// Copyright (c) 2022, Thomas Goyne +// +// Permission to use, copy, modify, and 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. + +#include + +namespace agi { +namespace detail { +inline void AppendStr(std::string& target, std::initializer_list strs) { + size_t size = 0; + for (auto str : strs) + size += str.size(); + target.reserve(target.size() + size); + for (auto str : strs) + target.append(str); +} +} + +template +void AppendJoin(std::string& str, std::string_view sep, Range const& range) +{ + auto begin = std::begin(range); + auto end = std::end(range); + if (begin == end) return; + + str += *begin++; + while (begin != end) { + str += sep; + str += *begin++; + } +} + +template +std::string Join(std::string_view sep, Range const& range) { + std::string str; + AppendJoin(str, sep, range); + return str; +} + +template +std::string Str(Args&&... args) { + std::string str; + detail::AppendStr(str, {args...}); + return str; +} + +template +void AppendStr(std::string& str, Args&&... args) { + detail::AppendStr(str, {args...}); +} +} // namespace agi diff --git a/libaegisub/include/libaegisub/thesaurus.h b/libaegisub/include/libaegisub/thesaurus.h index 887d59b13a..e0a4fc9fae 100644 --- a/libaegisub/include/libaegisub/thesaurus.h +++ b/libaegisub/include/libaegisub/thesaurus.h @@ -12,12 +12,12 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -#include "fs_fwd.h" - #include +#include #include #include #include +#include #include namespace agi { @@ -27,7 +27,7 @@ namespace charset { class IconvWrapper; } class Thesaurus { /// Map of word -> byte position in the data file - boost::container::flat_map offsets; + boost::container::flat_map> offsets; /// Read handle to the data file std::unique_ptr dat; /// Converter from the data file's charset to UTF-8 @@ -35,17 +35,17 @@ class Thesaurus { public: /// A pair of a word and synonyms for that word - typedef std::pair> Entry; + using Entry = std::pair>; /// Constructor /// @param dat_path Path to data file /// @param idx_path Path to index file - Thesaurus(agi::fs::path const& dat_path, agi::fs::path const& idx_path); + Thesaurus(std::filesystem::path const& dat_path, std::filesystem::path const& idx_path); ~Thesaurus(); /// Look up synonyms for a word /// @param word Word to look up - std::vector Lookup(std::string const& word); + std::vector Lookup(std::string_view word); }; } diff --git a/libaegisub/include/libaegisub/unicode.h b/libaegisub/include/libaegisub/unicode.h new file mode 100644 index 0000000000..5b3bbb4650 --- /dev/null +++ b/libaegisub/include/libaegisub/unicode.h @@ -0,0 +1,57 @@ +// Copyright (c) 2022, Thomas Goyne +// +// Permission to use, copy, modify, and 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. +// +// Aegisub Project http://www.aegisub.org/ + +#pragma once + +#include +#include +#include + +namespace agi { + +class BreakIterator { + std::unique_ptr bi; + std::string_view str; + size_t begin = 0, end = 0; + +public: + BreakIterator(); + + void set_text(std::string_view new_str); + bool done() const noexcept { return begin >= str.size(); } + bool is_last() const noexcept { return end >= str.size(); } + + void next() noexcept { + begin = end; + end = bi->next(); + } + + std::string_view current() const noexcept { + if (end == UBRK_DONE) return str.substr(begin); + return str.substr(begin, end - begin); + } + + std::string_view current_to_end() const noexcept { + return str.substr(begin); + } +}; + +struct UTextDeleter { + void operator()(UText *ut) { if (ut) utext_close(ut); } +}; +using UTextPtr = std::unique_ptr; + +} // namespace agi diff --git a/libaegisub/include/libaegisub/util.h b/libaegisub/include/libaegisub/util.h index 4aa34619f0..d4eb9350a7 100644 --- a/libaegisub/include/libaegisub/util.h +++ b/libaegisub/include/libaegisub/util.h @@ -15,19 +15,20 @@ #include #include #include +#include #include struct tm; -namespace agi { namespace util { +namespace agi::util { /// Clamp `b` to the range [`a`,`c`] template static inline T mid(T a, T b, T c) { return std::max(a, std::min(b, c)); } - bool try_parse(std::string const& str, double *out); - bool try_parse(std::string const& str, int *out); + bool try_parse(std::string_view str, double *out); + bool try_parse(std::string_view str, int *out); /// strftime, but on std::string rather than a fixed buffer /// @param fmt strftime format string @@ -75,9 +76,10 @@ namespace agi { namespace util { } std::string ErrorString(int error); + void InitLocale(); template auto range(Integer end) -> decltype(boost::irange(0, end)) { return boost::irange(0, end); } -} } // namespace agi::util +} // namespace agi::util diff --git a/libaegisub/include/libaegisub/util_osx.h b/libaegisub/include/libaegisub/util_osx.h index 3e4ce599e7..537d7e68df 100644 --- a/libaegisub/include/libaegisub/util_osx.h +++ b/libaegisub/include/libaegisub/util_osx.h @@ -31,15 +31,16 @@ #include namespace agi { - namespace osx { - class AppNapDisabler { - void *handle; - public: - AppNapDisabler(std::string reason); - ~AppNapDisabler(); - }; - } - namespace util { +namespace osx { +class AppNapDisabler { + void *handle; +public: + AppNapDisabler(std::string reason); + ~AppNapDisabler(); +}; +} + +namespace util { /// @brief Get the esources directory. /// @return Resources directory. /// @@ -54,5 +55,5 @@ std::string GetBundleResourcesDirectory(); std::string GetBundleSharedSupportDirectory(); std::string GetApplicationSupportDirectory(); - } // namespace util +} // namespace util } // namespace agi diff --git a/libaegisub/include/libaegisub/vfr.h b/libaegisub/include/libaegisub/vfr.h index 4f6e1b15d6..92f6a89c31 100644 --- a/libaegisub/include/libaegisub/vfr.h +++ b/libaegisub/include/libaegisub/vfr.h @@ -15,10 +15,10 @@ #pragma once #include +#include #include #include -#include namespace agi { /// Framerate handling. @@ -91,7 +91,7 @@ class Framerate { /// not the same thing as CFR X. When timecodes are loaded from a file, /// mkvmerge-style rounding is applied, while setting a constant frame rate /// uses truncation. - Framerate(fs::path const& filename); + Framerate(std::filesystem::path const& filename); /// @brief CFR constructor /// @param fps Frames per second or 0 for unloaded @@ -190,7 +190,7 @@ class Framerate { /// CFR, but saving CFR timecodes is a bit silly). Extra timecodes generated /// to hit length with v2 timecodes will monotonically increase but may not /// be otherwise sensible. - void Save(fs::path const& file, int length = -1) const; + void Save(std::filesystem::path const& file, int length = -1) const; /// Is this frame rate possibly variable? bool IsVFR() const {return timecodes.size() > 1; } diff --git a/libaegisub/lua/modules.cpp b/libaegisub/lua/modules.cpp index 061c0b8c05..61f32999fd 100644 --- a/libaegisub/lua/modules.cpp +++ b/libaegisub/lua/modules.cpp @@ -25,7 +25,7 @@ extern "C" int luaopen_unicode_impl(lua_State *L); extern "C" int luaopen_lfs_impl(lua_State *L); extern "C" int luaopen_lpeg(lua_State *L); -namespace agi { namespace lua { +namespace agi::lua { int regex_init(lua_State *L); void preload_modules(lua_State *L) { @@ -70,4 +70,4 @@ void do_register_lib_table(lua_State *L, std::initializer_list typ // leaves ffi.cast on the stack } -} } +} diff --git a/libaegisub/lua/modules/lfs.cpp b/libaegisub/lua/modules/lfs.cpp index 7698360f3d..4ef4c67d73 100644 --- a/libaegisub/lua/modules/lfs.cpp +++ b/libaegisub/lua/modules/lfs.cpp @@ -17,12 +17,11 @@ #include "libaegisub/fs.h" #include "libaegisub/lua/ffi.h" -#include -#include +#include using namespace agi::fs; using namespace agi::lua; -namespace bfs = boost::filesystem; +namespace bfs = std::filesystem; namespace agi { AGI_DEFINE_TYPE_NAME(DirectoryIterator); @@ -101,23 +100,26 @@ DirectoryIterator *dir_new(const char *path, char **err) { const char *get_mode(const char *path, char **err) { return wrap(err, [=]() -> const char * { + using enum bfs::file_type; switch (bfs::status(path).type()) { - case bfs::file_not_found: return nullptr; break; - case bfs::regular_file: return "file"; break; - case bfs::directory_file: return "directory"; break; - case bfs::symlink_file: return "link"; break; - case bfs::block_file: return "block device"; break; - case bfs::character_file: return "char device"; break; - case bfs::fifo_file: return "fifo"; break; - case bfs::socket_file: return "socket"; break; - case bfs::reparse_file: return "reparse point"; break; - default: return "other"; break; + case not_found: return nullptr; + case regular: return "file"; + case directory: return "directory"; + case symlink: return "link"; + case block: return "block device"; + case character: return "char device"; + case fifo: return "fifo"; + case socket: return "socket"; + default: return "other"; } }); } time_t get_mtime(const char *path, char **err) { - return wrap(err, [=] { return ModifiedTime(path); }); + return wrap(err, [=]() -> time_t { + using namespace std::chrono; + return duration_cast(ModifiedTime(path).time_since_epoch()).count(); + }); } uintmax_t get_size(const char *path, char **err) { diff --git a/libaegisub/lua/modules/re.cpp b/libaegisub/lua/modules/re.cpp index 66efe0f89d..7671f9dcc9 100644 --- a/libaegisub/lua/modules/re.cpp +++ b/libaegisub/lua/modules/re.cpp @@ -15,7 +15,6 @@ // Aegisub Project http://www.aegisub.org/ #include "libaegisub/lua/ffi.h" -#include "libaegisub/make_unique.h" #include @@ -49,7 +48,7 @@ bool search(u32regex& re, const char *str, size_t len, int start, boost::cmatch& } match *regex_match(u32regex& re, const char *str, size_t len, int start) { - auto result = agi::make_unique(); + auto result = std::make_unique(); if (!search(re, str, len, start, result->m)) return nullptr; return result.release(); @@ -96,7 +95,7 @@ char *regex_replace(u32regex& re, const char *replacement, const char *str, size } u32regex *regex_compile(const char *pattern, int flags, char **err) { - auto re = agi::make_unique(); + auto re = std::make_unique(); try { *re = boost::make_u32regex(pattern, boost::u32regex::perl | flags); return re.release(); diff --git a/libaegisub/lua/modules/unicode.cpp b/libaegisub/lua/modules/unicode.cpp index d9a6c2248b..9ee64b1143 100644 --- a/libaegisub/lua/modules/unicode.cpp +++ b/libaegisub/lua/modules/unicode.cpp @@ -16,24 +16,34 @@ #include -#include +#include namespace { -template -char *wrap(const char *str, char **err) { - try { - return agi::lua::strndup(func(str, std::locale())); - } catch (std::exception const& e) { - *err = strdup(e.what()); - return nullptr; +char *wrap(void (*fn)(icu::UnicodeString&), const char *str, char **err) { + auto ustr = icu::UnicodeString::fromUTF8(str); + if (ustr.isBogus()) { + *err = strdup((std::string("Converting \"") + str + "\" to a unicode string failed.").c_str()); } + fn(ustr); + std::string out; + ustr.toUTF8String(out); + return agi::lua::strndup(out); } + +template +char *wrap(const char *str, char **err) { + return wrap(fn, str, err); +} + +void to_upper(icu::UnicodeString& str) { str.toUpper(); } +void to_lower(icu::UnicodeString& str) { str.toLower(); } +void to_fold(icu::UnicodeString& str) { str.foldCase(); } } extern "C" int luaopen_unicode_impl(lua_State *L) { agi::lua::register_lib_table(L, {}, - "to_upper_case", wrap>, - "to_lower_case", wrap>, - "to_fold_case", wrap>); + "to_upper_case", wrap, + "to_lower_case", wrap, + "to_fold_case", wrap); return 1; } diff --git a/libaegisub/lua/script_reader.cpp b/libaegisub/lua/script_reader.cpp index 6beb9813b6..95d0278f64 100644 --- a/libaegisub/lua/script_reader.cpp +++ b/libaegisub/lua/script_reader.cpp @@ -24,8 +24,8 @@ #include #include -namespace agi { namespace lua { - bool LoadFile(lua_State *L, agi::fs::path const& raw_filename) { +namespace agi::lua { + bool LoadFile(lua_State *L, std::filesystem::path const& raw_filename) { auto filename = raw_filename; try { filename = agi::fs::Canonicalize(raw_filename); @@ -36,7 +36,7 @@ namespace agi { namespace lua { agi::read_file_mapping file(filename); auto buff = file.read(); - size_t size = static_cast(file.size()); + auto size = static_cast(file.size()); // Discard the BOM if present if (size >= 3 && buff[0] == -17 && buff[1] == -69 && buff[2] == -65) { @@ -89,9 +89,9 @@ namespace agi { namespace lua { // If there's a .moon file at that path, load it instead of the // .lua file - agi::fs::path path = filename; + std::filesystem::path path = filename; if (agi::fs::HasExtension(path, "lua")) { - agi::fs::path moonpath = path; + std::filesystem::path moonpath = path; moonpath.replace_extension("moon"); if (agi::fs::FileExists(moonpath)) path = moonpath; @@ -162,4 +162,4 @@ namespace agi { namespace lua { return true; } -} } +} diff --git a/libaegisub/lua/utils.cpp b/libaegisub/lua/utils.cpp index 2cde75f463..246871d7bc 100644 --- a/libaegisub/lua/utils.cpp +++ b/libaegisub/lua/utils.cpp @@ -20,16 +20,16 @@ #include "libaegisub/log.h" #include -#include #include -#include + +#include #ifdef _MSC_VER // Disable warnings for noreturn functions having return types #pragma warning(disable: 4645 4646) #endif -namespace agi { namespace lua { +namespace agi::lua { std::string get_string_or_default(lua_State *L, int idx) { size_t len = 0; const char *str = lua_tolstring(L, idx, &len); @@ -60,7 +60,7 @@ std::string check_string(lua_State *L, int idx) { return std::string(str, len); } -int check_int(lua_State *L, int idx) { +long check_int(lua_State *L, int idx) { auto v = lua_tointeger(L, idx); if (v == 0 && !lua_isnumber(L, idx)) typerror(L, idx, "number"); @@ -140,8 +140,8 @@ int add_stack_trace(lua_State *L) { // Strip the location from the error message since it's redundant with // the stack trace - boost::regex location(R"(^\[string ".*"\]:[0-9]+: )"); - message = regex_replace(message, location, "", boost::format_first_only); + std::regex location(R"(^\[string ".*"\]:[0-9]+: )"); + message = regex_replace(message, location, "", std::regex_constants::format_first_only); std::vector frames; frames.emplace_back(std::move(message)); @@ -157,7 +157,7 @@ int add_stack_trace(lua_State *L) { std::string file = ar.source; if (file == "=[C]") file = ""; - else if (boost::ends_with(file, ".moon")) + else if (file.ends_with(".moon")) is_moon = true; auto real_line = [&](int line) { @@ -181,7 +181,7 @@ int add_stack_trace(lua_State *L) { return 1; } -int BOOST_NORETURN error(lua_State *L, const char *fmt, ...) { +[[noreturn]] int error(lua_State *L, const char *fmt, ...) { va_list argp; va_start(argp, fmt); luaL_where(L, 1); @@ -191,7 +191,7 @@ int BOOST_NORETURN error(lua_State *L, const char *fmt, ...) { throw error_tag(); } -int BOOST_NORETURN argerror(lua_State *L, int narg, const char *extramsg) { +[[noreturn]] int argerror(lua_State *L, int narg, const char *extramsg) { lua_Debug ar; if (!lua_getstack(L, 0, &ar)) error(L, "bad argument #%d (%s)", narg, extramsg); @@ -203,7 +203,7 @@ int BOOST_NORETURN argerror(lua_State *L, int narg, const char *extramsg) { narg, ar.name, extramsg); } -int BOOST_NORETURN typerror(lua_State *L, int narg, const char *tname) { +[[noreturn]] int typerror(lua_State *L, int narg, const char *tname) { const char *msg = lua_pushfstring(L, "%s expected, got %s", tname, luaL_typename(L, narg)); argerror(L, narg, msg); @@ -261,4 +261,11 @@ void LuaStackcheck::dump() { #endif } + +namespace boost::locale::impl_icu { +struct cdata; +std::locale create_parsing(std::locale const& in, cdata const&, uint32_t) { return in; } +std::locale create_boundary(std::locale const& in, cdata const&, uint32_t) { return in; } +std::locale create_calendar(std::locale const& in, cdata const&) { return in; } +std::locale create_formatting(std::locale const& in, cdata const&, uint32_t) { return in; } } diff --git a/libaegisub/meson.build b/libaegisub/meson.build index e780a399ca..7689d31aeb 100644 --- a/libaegisub/meson.build +++ b/libaegisub/meson.build @@ -1,5 +1,6 @@ libaegisub_src = [ 'ass/dialogue_parser.cpp', + 'ass/karaoke.cpp', 'ass/time.cpp', 'ass/uuencode.cpp', @@ -34,6 +35,7 @@ libaegisub_src = [ 'common/parser.cpp', 'common/path.cpp', 'common/thesaurus.cpp', + 'common/unicode.cpp', 'common/util.cpp', 'common/vfr.cpp', 'common/ycbcr_conv.cpp', diff --git a/libaegisub/osx/dispatch.mm b/libaegisub/osx/dispatch.mm index afb3c31316..d5f5aaf431 100644 --- a/libaegisub/osx/dispatch.mm +++ b/libaegisub/osx/dispatch.mm @@ -24,20 +24,20 @@ std::function invoke_main; struct OSXQueue : Queue { - virtual void DoSync(Thunk thunk)=0; + virtual void DoSync(Thunk&& thunk)=0; }; struct MainQueue final : OSXQueue { - void DoInvoke(Thunk thunk) override { invoke_main(thunk); } + void DoInvoke(Thunk&& thunk) override { invoke_main(std::move(thunk)); } - void DoSync(Thunk thunk) override { + void DoSync(Thunk&& thunk) override { std::mutex m; std::condition_variable cv; - std::unique_lock l(m); + std::unique_lock l(m); std::exception_ptr e; bool done = false; invoke_main([&]{ - std::unique_lock l(m); + std::unique_lock l(m); try { thunk(); } @@ -57,8 +57,8 @@ void DoSync(Thunk thunk) override { GCDQueue(dispatch_queue_t queue) : queue(queue) { } ~GCDQueue() { dispatch_release(queue); } - void DoInvoke(Thunk thunk) override { - dispatch_async(queue, ^{ + void DoInvoke(Thunk&& thunk) override { + dispatch_async(queue, [thunk] { try { thunk(); } @@ -69,7 +69,7 @@ void DoInvoke(Thunk thunk) override { }); } - void DoSync(Thunk thunk) override { + void DoSync(Thunk&& thunk) override { std::exception_ptr e; std::exception_ptr *e_ptr = &e; dispatch_sync(queue, ^{ @@ -85,13 +85,13 @@ void DoSync(Thunk thunk) override { }; } -namespace agi { namespace dispatch { -void Init(std::function invoke_main) { +namespace agi::dispatch { +void Init(std::function&& invoke_main) { ::invoke_main = std::move(invoke_main); } -void Queue::Async(Thunk thunk) { DoInvoke(std::move(thunk)); } -void Queue::Sync(Thunk thunk) { static_cast(this)->DoSync(std::move(thunk)); } +void Queue::Async(Thunk&& thunk) { DoInvoke(std::move(thunk)); } +void Queue::Sync(Thunk&& thunk) { static_cast(this)->DoSync(std::move(thunk)); } Queue& Main() { static MainQueue q; @@ -104,7 +104,6 @@ void Init(std::function invoke_main) { } std::unique_ptr Create() { - return std::unique_ptr(new GCDQueue(dispatch_queue_create("Aegisub worker queue", - DISPATCH_QUEUE_SERIAL))); + return std::make_unique(dispatch_queue_create("Aegisub worker queue", DISPATCH_QUEUE_SERIAL)); } -} } +} // agi::dispatch diff --git a/libaegisub/osx/spellchecker.mm b/libaegisub/osx/spellchecker.mm index ea7cb3d871..670e0a19cc 100644 --- a/libaegisub/osx/spellchecker.mm +++ b/libaegisub/osx/spellchecker.mm @@ -37,13 +37,17 @@ template using objc_ptr = std::unique_ptr; +NSString *ns_str(std::string_view str) { + return [[[NSString alloc] initWithBytes:str.data() length:str.length() encoding:NSUTF8StringEncoding] autorelease]; +} + class CocoaSpellChecker final : public agi::SpellChecker { + NSSpellChecker *spellChecker = NSSpellChecker.sharedSpellChecker; objc_ptr language; agi::signal::Connection lang_listener; void OnLanguageChanged(agi::OptionValue const& opt) { - language.reset([[NSString alloc] initWithCString:opt.GetString().c_str() - encoding:NSUTF8StringEncoding]); + language.reset(ns_str(opt.GetString()).retain); } public: @@ -54,56 +58,55 @@ void OnLanguageChanged(agi::OptionValue const& opt) { } std::vector GetLanguageList() override { - return array_to_vector([[NSSpellChecker sharedSpellChecker] availableLanguages]); + return array_to_vector(NSSpellChecker.sharedSpellChecker.availableLanguages); } - bool CheckWord(std::string const& word) override { - auto str = [NSString stringWithUTF8String:word.c_str()]; - auto range = [NSSpellChecker.sharedSpellChecker checkSpellingOfString:str - startingAt:0 - language:language.get() - wrap:NO - inSpellDocumentWithTag:0 - wordCount:nullptr]; + bool CheckWord(std::string_view word) override { + auto range = [spellChecker checkSpellingOfString:ns_str(word) + startingAt:0 + language:language.get() + wrap:NO + inSpellDocumentWithTag:0 + wordCount:nullptr]; return range.location == NSNotFound; } - std::vector GetSuggestions(std::string const& word) override { - auto str = [NSString stringWithUTF8String:word.c_str()]; - auto range = [NSSpellChecker.sharedSpellChecker checkSpellingOfString:str - startingAt:0 - language:language.get() - wrap:NO - inSpellDocumentWithTag:0 - wordCount:nullptr]; - return array_to_vector([NSSpellChecker.sharedSpellChecker guessesForWordRange:range - inString:str - language:language.get() - inSpellDocumentWithTag:0]); + std::vector GetSuggestions(std::string_view word) override { + auto str = ns_str(word); + auto range = [spellChecker checkSpellingOfString:str + startingAt:0 + language:language.get() + wrap:NO + inSpellDocumentWithTag:0 + wordCount:nullptr]; + return array_to_vector([spellChecker guessesForWordRange:range + inString:str + language:language.get() + inSpellDocumentWithTag:0]); } - bool CanAddWord(std::string const&) override { + bool CanAddWord(std::string_view) override { return true; } - bool CanRemoveWord(std::string const& str) override { - return [NSSpellChecker.sharedSpellChecker hasLearnedWord:[NSString stringWithUTF8String:str.c_str()]]; + bool CanRemoveWord(std::string_view str) override { + return [spellChecker hasLearnedWord:ns_str(str)]; } - void AddWord(std::string const& word) override { - NSSpellChecker.sharedSpellChecker.language = language.get(); - [NSSpellChecker.sharedSpellChecker learnWord:[NSString stringWithUTF8String:word.c_str()]]; + void AddWord(std::string_view word) override { + spellChecker.language = language.get(); + [spellChecker learnWord:ns_str(word)]; } - void RemoveWord(std::string const& word) override { - NSSpellChecker.sharedSpellChecker.language = language.get(); - [NSSpellChecker.sharedSpellChecker unlearnWord:[NSString stringWithUTF8String:word.c_str()]]; + void RemoveWord(std::string_view word) override { + spellChecker.language = language.get(); + [spellChecker unlearnWord:ns_str(word)]; } }; } namespace agi { std::unique_ptr CreateCocoaSpellChecker(OptionValue *opt) { - return std::unique_ptr(new CocoaSpellChecker(opt)); + return std::make_unique(opt); } } diff --git a/libaegisub/osx/util.mm b/libaegisub/osx/util.mm index f211d6f464..e6b77dbc4a 100644 --- a/libaegisub/osx/util.mm +++ b/libaegisub/osx/util.mm @@ -22,10 +22,10 @@ #import static std::string EmptyIfNil(NSString *string) { - return string ? [string UTF8String] : ""; + return [string UTF8String] ?: ""; } -namespace agi { namespace osx { +namespace agi::osx { AppNapDisabler::AppNapDisabler(std::string reason) : handle(nullptr) { if (reason.empty()) reason = "Loading"; auto processInfo = [NSProcessInfo processInfo]; @@ -41,10 +41,9 @@ [processInfo endActivity:(id)handle]; [(id)handle release]; } +} // namespace agi::osx -} -namespace util { - +namespace agi::util { std::string GetBundleResourcesDirectory() { @autoreleasepool { return EmptyIfNil([[[NSBundle mainBundle] resourceURL] path]); @@ -63,4 +62,4 @@ } } -} } +} // namespace agi::util diff --git a/libaegisub/unix/access.cpp b/libaegisub/unix/access.cpp index 73da19d224..a8f42131c8 100644 --- a/libaegisub/unix/access.cpp +++ b/libaegisub/unix/access.cpp @@ -12,54 +12,49 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -/// @file access.cpp -/// @brief Unix access methods. -/// @ingroup libaegisub unix - #include "libaegisub/access.h" #include "libaegisub/fs.h" +#include #include -#include +#include #include -#include - -namespace agi { - namespace acs { - -void Check(agi::fs::path const& file, acs::Type type) { - struct stat file_stat; - - int file_status = stat(file.c_str(), &file_stat); +namespace agi::acs { - if (file_status != 0) { - switch (errno) { - case ENOENT: - throw fs::FileNotFound(file); - case EACCES: - throw fs::ReadDenied(file); - case EIO: +void Check(std::filesystem::path const& file, acs::Type type) { + auto cu = std::filesystem::current_path(); + std::error_code ec; + auto s = std::filesystem::status(file, ec); + if (ec != std::error_code{}) { + using enum std::errc; + switch (ec.value()) { + case int(no_such_file_or_directory): throw fs::FileNotFound(file); + case int(permission_denied): throw fs::ReadDenied(file); + case int(io_error): throw fs::FileSystemUnknownError("Fatal I/O error in 'stat' on path: " + file.string()); + default: + throw fs::FileSystemUnknownError("Fatal I/O error in 'stat' on path: " + file.string() + ": " + ec.message()); } } + using std::filesystem::file_type; switch (type) { case FileRead: case FileWrite: - if ((file_stat.st_mode & S_IFREG) == 0) + if (s.type() != file_type::regular) throw fs::NotAFile(file); break; case DirRead: case DirWrite: - if ((file_stat.st_mode & S_IFDIR) == 0) + if (s.type() != file_type::directory) throw fs::NotADirectory(file); break; } - file_status = access(file.c_str(), R_OK); + int file_status = access(file.c_str(), R_OK); if (file_status != 0) throw fs::ReadDenied(file); @@ -70,5 +65,4 @@ void Check(agi::fs::path const& file, acs::Type type) { } } - } // namespace acs -} // namespace agi +} // namespace agi::acs diff --git a/libaegisub/unix/fs.cpp b/libaegisub/unix/fs.cpp index 8f6ead6a89..a9adb12a61 100644 --- a/libaegisub/unix/fs.cpp +++ b/libaegisub/unix/fs.cpp @@ -18,16 +18,15 @@ #include "libaegisub/fs.h" #include "libaegisub/io.h" -#include -#include #include +#include #include #include #include +#include -namespace bfs = boost::filesystem; - -namespace agi { namespace fs { +namespace sfs = std::filesystem; +namespace agi::fs { std::string ShortName(path const& p) { return p.string(); } @@ -38,7 +37,7 @@ void Touch(path const& file) { int fd = open(file.c_str(), O_CREAT | O_APPEND | O_WRONLY, 0644); if (fd >= 0) { futimes(fd, nullptr); - close(fd); + ::close(fd); } } @@ -52,23 +51,23 @@ void Copy(fs::path const& from, fs::path const& to) { } struct DirectoryIterator::PrivData { - boost::system::error_code ec; - bfs::directory_iterator it; + std::error_code ec; + sfs::directory_iterator it; std::string filter; PrivData(path const& p, std::string const& filter) : it(p, ec), filter(filter) { } bool bad() const { return - it == bfs::directory_iterator() || + it == sfs::directory_iterator() || (!filter.empty() && fnmatch(filter.c_str(), it->path().filename().c_str(), 0)); } }; -DirectoryIterator::DirectoryIterator() { } +DirectoryIterator::DirectoryIterator() = default; DirectoryIterator::DirectoryIterator(path const& p, std::string const& filter) -: privdata(new PrivData(p, filter)) +: privdata(std::make_unique(p, filter)) { - if (privdata->it == bfs::directory_iterator()) + if (privdata->it == sfs::directory_iterator()) privdata.reset(); else if (privdata->bad()) ++*this; @@ -86,7 +85,7 @@ DirectoryIterator& DirectoryIterator::operator++() { ++privdata->it; while (privdata->bad()) { - if (privdata->it == bfs::directory_iterator()) { + if (privdata->it == sfs::directory_iterator()) { privdata.reset(); return *this; } @@ -98,6 +97,6 @@ DirectoryIterator& DirectoryIterator::operator++() { return *this; } -DirectoryIterator::~DirectoryIterator() { } +DirectoryIterator::~DirectoryIterator() = default; -} } +} diff --git a/libaegisub/unix/log.cpp b/libaegisub/unix/log.cpp index 82109dd2b6..b53e1d2617 100644 --- a/libaegisub/unix/log.cpp +++ b/libaegisub/unix/log.cpp @@ -18,7 +18,7 @@ #include #include -namespace agi { namespace log { +namespace agi::log { void EmitSTDOUT::log(SinkMessage const& sm) { time_t time = sm.time / 1000000000; tm tmtime; @@ -40,4 +40,4 @@ void EmitSTDOUT::log(SinkMessage const& sm) { if (!isatty(fileno(stdout))) fflush(stdout); } -} } +} diff --git a/libaegisub/unix/path.cpp b/libaegisub/unix/path.cpp index 8fa14cd190..a171bc2a43 100644 --- a/libaegisub/unix/path.cpp +++ b/libaegisub/unix/path.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #ifndef __APPLE__ @@ -70,12 +69,12 @@ std::string exe_dir() { namespace agi { void Path::FillPlatformSpecificPaths() { #ifndef __APPLE__ - agi::fs::path home = home_dir(); + std::filesystem::path home = home_dir(); SetToken("?user", home/".aegisub"); SetToken("?local", home/".aegisub"); #ifdef APPIMAGE_BUILD - agi::fs::path data = exe_dir(); + std::filesystem::path data = exe_dir(); if (data == "") data = home/".aegisub"; SetToken("?data", data); SetToken("?dictionary", Decode("?data/dictionaries")); @@ -85,13 +84,13 @@ void Path::FillPlatformSpecificPaths() { #endif #else - agi::fs::path app_support = agi::util::GetApplicationSupportDirectory(); + std::filesystem::path app_support = agi::util::GetApplicationSupportDirectory(); SetToken("?user", app_support/"Aegisub"); SetToken("?local", app_support/"Aegisub"); SetToken("?data", agi::util::GetBundleSharedSupportDirectory()); SetToken("?dictionary", Decode("?data/dictionaries")); #endif - SetToken("?temp", boost::filesystem::temp_directory_path()); + SetToken("?temp", std::filesystem::temp_directory_path()); } } diff --git a/libaegisub/unix/util.cpp b/libaegisub/unix/util.cpp index 4cf6721198..e1b9bea604 100644 --- a/libaegisub/unix/util.cpp +++ b/libaegisub/unix/util.cpp @@ -15,22 +15,12 @@ #include #include - -#ifdef _LIBCPP_VERSION #include -#else -#include -#endif -namespace agi { namespace util { +namespace agi::util { void SetThreadName(const char *) { } void sleep_for(int ms) { -#ifdef __clang__ std::this_thread::sleep_for(std::chrono::milliseconds(ms)); -#else - boost::this_thread::sleep_for(boost::chrono::milliseconds(ms)); -#endif } - -} } +} diff --git a/libaegisub/windows/charset_conv_win.cpp b/libaegisub/windows/charset_conv_win.cpp index 67385001a2..c0e397fcf4 100644 --- a/libaegisub/windows/charset_conv_win.cpp +++ b/libaegisub/windows/charset_conv_win.cpp @@ -30,8 +30,7 @@ std::string from_w(agi::charset::IconvWrapper &w32Conv, std::wstring const& sour } } -namespace agi { - namespace charset { +namespace agi::charset { std::wstring ConvertW(std::string const& source) { static IconvWrapper w32Conv("utf-8", "utf-16le", false); @@ -54,4 +53,3 @@ std::string ConvertLocal(std::wstring const& source) { } } -} diff --git a/libaegisub/windows/log_win.cpp b/libaegisub/windows/log_win.cpp index 91017a8f52..01120bd237 100644 --- a/libaegisub/windows/log_win.cpp +++ b/libaegisub/windows/log_win.cpp @@ -15,7 +15,6 @@ #include "libaegisub/log.h" #include "libaegisub/charset_conv_win.h" -#include "libaegisub/make_unique.h" #define WIN32_LEAN_AND_MEAN #include diff --git a/libaegisub/windows/path_win.cpp b/libaegisub/windows/path_win.cpp index 387cf1cdd3..22120f3ff4 100644 --- a/libaegisub/windows/path_win.cpp +++ b/libaegisub/windows/path_win.cpp @@ -21,7 +21,6 @@ #include #include -#include #include diff --git a/meson.build b/meson.build index 4834c8224d..f451019626 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project('Aegisub', ['c', 'cpp'], license: 'BSD-3-Clause', meson_version: '>=0.56.1', - default_options: ['cpp_std=c++14', 'buildtype=debugoptimized'], + default_options: ['cpp_std=c++20', 'buildtype=debugoptimized'], version: '3.2.2') cmake = import('cmake') @@ -67,7 +67,7 @@ if host_machine.system() == 'darwin' add_languages('objc', 'objcpp') add_project_arguments('-DGL_SILENCE_DEPRECATION', language: 'cpp') # meson neither supports objcpp_std nor inherits cpp_std https://github.com/mesonbuild/meson/issues/5495 - add_project_arguments('-std=c++11', language: 'objcpp') + add_project_arguments('-std=c++20', language: 'objcpp') elif host_machine.system() != 'windows' conf.set('WITH_FONTCONFIG', 1) deps += dependency('fontconfig') @@ -94,7 +94,7 @@ deps += dependency('libass', version: '>=0.9.7', boost_modules = ['chrono', 'filesystem', 'thread', 'locale', 'regex'] if not get_option('local_boost') - boost_dep = dependency('boost', version: '>=1.50.0', + boost_dep = dependency('boost', version: '>=1.70.0', modules: boost_modules + ['system'], required: false, static: get_option('default_library') == 'static') @@ -117,7 +117,7 @@ deps += dependency('zlib') wx_minver = '>=' + get_option('wx_version') if host_machine.system() == 'darwin' - wx_minver = '>=3.1.0' + wx_minver = '>=3.2.0' endif wx_dep = dependency('wxWidgets', version: wx_minver, required: false, @@ -137,36 +137,275 @@ else opt_var = cmake.subproject_options() opt_var.add_cmake_defines({ - 'wxBUILD_INSTALL': false, - 'wxBUILD_PRECOMP': false, # otherwise breaks project generation w/ meson + 'CMAKE_BUILD_TYPE': build_type, + + 'wxBUILD_COMPATIBILITY': '3.1', + 'wxBUILD_INSTALL': 'OFF', + 'wxBUILD_PRECOMP': 'OFF', # otherwise breaks project generation w/ meson + 'wxBUILD_MONOLITHIC': 'ON', # otherwise breaks project generation w/ meson 'wxBUILD_SHARED': build_shared, - 'wxUSE_WEBVIEW': false, # breaks build on linux - 'CMAKE_BUILD_TYPE': build_type, - 'wxUSE_IMAGE': true, - 'wxBUILD_MONOLITHIC': true # otherwise breaks project generation w/ meson + 'wxDIALOG_UNIT_COMPATIBILITY': 0, + 'wxUSE_ON_FATAL_EXCEPTION': 1, + 'wxUSE_STACKWALKER': 0, + 'wxUSE_DEBUGREPORT': 0, + 'wxUSE_DEBUG_CONTEXT': 0, + 'wxUSE_MEMORY_TRACING': 0, + 'wxUSE_GLOBAL_MEMORY_OPERATORS': 0, + 'wxUSE_DEBUG_NEW_ALWAYS': 0, + 'wxUSE_UNICODE': 1, + 'wxUSE_WCHAR_T': 1, + 'wxUSE_EXCEPTIONS': 1, + 'wxUSE_EXTENDED_RTTI': 0, + 'wxUSE_STL': 0, + 'wxUSE_LOG': 1, + 'wxUSE_LOGWINDOW': 1, + 'wxUSE_LOGGUI': 1, + 'wxUSE_LOG_DIALOG': 1, + 'wxUSE_CMDLINE_PARSER': 0, + 'wxUSE_THREADS': 1, + 'wxUSE_STREAMS': 1, + 'wxUSE_STD_IOSTREAM': 0, + 'wxUSE_STD_STRING': 1, + 'wxUSE_PRINTF_POS_PARAMS': 1, + 'wxUSE_LONGLONG': 1, + 'wxUSE_BASE64': 0, + 'wxUSE_CONSOLE_EVENTLOOP': 1, + 'wxUSE_FILE': 1, + 'wxUSE_FFILE': 1, + 'wxUSE_FSVOLUME': 1, + 'wxUSE_STDPATHS': 1, + 'wxUSE_TEXTBUFFER': 0, + 'wxUSE_TEXTFILE': 0, + 'wxUSE_INTL': 1, + 'wxUSE_XLOCALE': 1, + 'wxUSE_DATETIME': 1, + 'wxUSE_TIMER': 1, + 'wxUSE_STOPWATCH': 1, + 'wxUSE_FSWATCHER': 0, + 'wxUSE_CONFIG': 0, + 'wxUSE_CONFIG_NATIVE': 0, + 'wxUSE_DIALUP_MANAGER': 0, + 'wxUSE_DYNLIB_CLASS': 1, + 'wxUSE_DYNAMIC_LOADER': 1, + 'wxUSE_SOCKETS': 0, + 'wxUSE_IPV6': 0, + 'wxUSE_FILESYSTEM': 0, + 'wxUSE_FS_ZIP': 0, + 'wxUSE_FS_ARCHIVE': 0, + 'wxUSE_FS_INET': 0, + 'wxUSE_ARCHIVE_STREAMS': 1, + 'wxUSE_ZIPSTREAM': 1, + 'wxUSE_TARSTREAM': 0, + 'wxUSE_APPLE_IEEE': 0, + 'wxUSE_JOYSTICK': 0, + 'wxUSE_FONTENUM': 1, + 'wxUSE_FONTMAP': 1, + 'wxUSE_MIMETYPE': 0, + 'wxUSE_PROTOCOL': 0, + 'wxUSE_PROTOCOL_FILE': 0, + 'wxUSE_PROTOCOL_FTP': 0, + 'wxUSE_PROTOCOL_HTTP': 0, + 'wxUSE_URL': 0, + 'wxUSE_URL_NATIVE': 0, + 'wxUSE_VARIANT': 1, + 'wxUSE_ANY': 0, + 'wxUSE_REGEX': 'OFF', + 'wxUSE_SYSTEM_OPTIONS': 1, + 'wxUSE_SOUND': 0, + 'wxUSE_MEDIACTRL': 0, + 'wxUSE_GSTREAMER': 0, + 'wxUSE_XRC': 0, + 'wxUSE_XML': 0, + 'wxUSE_AUI': 0, + 'wxUSE_RIBBON': 0, + 'wxUSE_PROPGRID': 0, + 'wxUSE_STC': 1, + 'wxUSE_GRAPHICS_CONTEXT': 1, + 'wxUSE_GRAPHICS_GDIPLUS': 1, + 'wxUSE_CONTROLS': 1, + 'wxUSE_POPUPWIN': 1, + 'wxUSE_TIPWINDOW': 0, + 'wxUSE_ANIMATIONCTRL': 0, + 'wxUSE_BUTTON': 1, + 'wxUSE_BMPBUTTON': 1, + 'wxUSE_CALENDARCTRL': 1, + 'wxUSE_CHECKBOX': 1, + 'wxUSE_CHECKLISTBOX': 1, + 'wxUSE_CHOICE': 1, + 'wxUSE_COLLPANE': 0, + 'wxUSE_COLOURPICKERCTRL': 1, + 'wxUSE_COMBOBOX': 1, + 'wxUSE_DATAVIEWCTRL': 1, + 'wxUSE_DATEPICKCTRL': 1, + 'wxUSE_DIRPICKERCTRL': 1, + 'wxUSE_EDITABLELISTBOX': 1, + 'wxUSE_FILECTRL': 1, + 'wxUSE_FILEPICKERCTRL': 1, + 'wxUSE_FONTPICKERCTRL': 1, + 'wxUSE_GAUGE': 1, + 'wxUSE_HEADERCTRL': 1, + 'wxUSE_HYPERLINKCTRL': 1, + 'wxUSE_LISTBOX': 1, + 'wxUSE_LISTCTRL': 1, + 'wxUSE_RADIOBOX': 1, + 'wxUSE_RADIOBTN': 1, + 'wxUSE_SCROLLBAR': 1, + 'wxUSE_SEARCHCTRL': 1, + 'wxUSE_SLIDER': 1, + 'wxUSE_SPINBTN': 1, + 'wxUSE_SPINCTRL': 1, + 'wxUSE_STATBOX': 1, + 'wxUSE_STATLINE': 1, + 'wxUSE_STATTEXT': 1, + 'wxUSE_STATBMP': 1, + 'wxUSE_TEXTCTRL': 1, + 'wxUSE_TOGGLEBTN': 1, + 'wxUSE_TREECTRL': 1, + 'wxUSE_STATUSBAR': 1, + 'wxUSE_NATIVE_STATUSBAR': 1, + 'wxUSE_TOOLBAR': 1, + 'wxUSE_TOOLBAR_NATIVE': 1, + 'wxUSE_NOTEBOOK': 1, + 'wxUSE_LISTBOOK': 1, + 'wxUSE_CHOICEBOOK': 1, + 'wxUSE_TREEBOOK': 1, + 'wxUSE_TOOLBOOK': 1, + 'wxUSE_TASKBARICON': 0, + 'wxUSE_TAB_DIALOG': 0, + 'wxUSE_GRID': 0, + 'wxUSE_MINIFRAME': 0, + 'wxUSE_COMBOCTRL': 1, + 'wxUSE_ODCOMBOBOX': 1, + 'wxUSE_BITMAPCOMBOBOX': 0, + 'wxUSE_REARRANGECTRL': 1, + 'wxUSE_ACCEL': 1, + 'wxUSE_HOTKEY': 1, + 'wxUSE_CARET': 1, + 'wxUSE_DISPLAY': 1, + 'wxUSE_GEOMETRY': 1, + 'wxUSE_IMAGLIST': 1, + 'wxUSE_INFOBAR': 1, + 'wxUSE_MENUS': 1, + 'wxUSE_NOTIFICATION_MESSAGE': 0, + 'wxUSE_SASH': 1, + 'wxUSE_SPLITTER': 1, + 'wxUSE_TOOLTIPS': 1, + 'wxUSE_VALIDATORS': 1, + 'wxUSE_COMMON_DIALOGS': 1, + 'wxUSE_BUSYINFO': 0, + 'wxUSE_CHOICEDLG': 1, + 'wxUSE_COLOURDLG': 1, + 'wxUSE_DIRDLG': 1, + 'wxUSE_FILEDLG': 1, + 'wxUSE_FINDREPLDLG': 0, + 'wxUSE_FONTDLG': 1, + 'wxUSE_MSGDLG': 1, + 'wxUSE_PROGRESSDLG': 0, + 'wxUSE_STARTUP_TIPS': 0, + 'wxUSE_TEXTDLG': 1, + 'wxUSE_NUMBERDLG': 1, + 'wxUSE_SPLASH': 0, + 'wxUSE_WIZARDDLG': 0, + 'wxUSE_ABOUTDLG': 1, + 'wxUSE_METAFILE': 0, + 'wxUSE_ENH_METAFILE': 0, + 'wxUSE_WIN_METAFILES_ALWAYS': 0, + 'wxUSE_MDI': 0, + 'wxUSE_DOC_VIEW_ARCHITECTURE': 0, + 'wxUSE_MDI_ARCHITECTURE': 0, + 'wxUSE_PRINTING_ARCHITECTURE': 0, + 'wxUSE_HTML': 0, + 'wxUSE_GLCANVAS': 1, + 'wxUSE_RICHTEXT': 0, + 'wxUSE_CLIPBOARD': 1, + 'wxUSE_DATAOBJ': 1, + 'wxUSE_DRAG_AND_DROP': 1, + 'wxUSE_ACCESSIBILITY': 0, + 'wxUSE_SNGLINST_CHECKER': 1, + 'wxUSE_DRAGIMAGE': 1, + 'wxUSE_IPC': 0, + 'wxUSE_HELP': 0, + 'wxUSE_MS_HTML_HELP': 0, + 'wxUSE_WXHTML_HELP': 0, + 'wxUSE_RESOURCES': 0, + 'wxUSE_CONSTRAINTS': 0, + 'wxUSE_SPLINES': 0, + 'wxUSE_MOUSEWHEEL': 1, + 'wxUSE_POSTSCRIPT': 0, + 'wxUSE_AFM_FOR_POSTSCRIPT': 0, + 'wxUSE_SVG': 0, + 'wxODBC_FWD_ONLY_CURSORS': false, + 'wxODBC_BACKWARD_COMPATABILITY': false, + 'wxUSE_IOSTREAMH': 0, + 'wxUSE_IMAGE': 1, + 'wxUSE_LIBJPEG': 'OFF', + 'wxUSE_LIBTIFF': 'OFF', + 'wxUSE_TGA': 'OFF', + 'wxUSE_GIF': 'OFF', + 'wxUSE_PNM': 'OFF', + 'wxUSE_PCX': 'OFF', + 'wxUSE_IFF': 'OFF', + 'wxUSE_XPM': 'OFF', + 'wxUSE_ICO_CUR': 1, + 'wxUSE_PALETTE': 1, + 'wxUSE_ALL_THEMES': 1, + 'wxUSE_UNICODE_MSLU': 0, + 'wxUSE_MFC': 0, + 'wxUSE_OLE': 1, + 'wxUSE_OLE_AUTOMATION': 0, + 'wxUSE_ACTIVEX': 0, + 'wxUSE_DC_CACHEING': 1, + 'wxUSE_DIB_FOR_BITMAP': 0, + 'wxUSE_WXDIB': 1, + 'wxUSE_POSTSCRIPT_ARCHITECTURE_IN_MSW': 0, + 'wxUSE_REGKEY': 1, + 'wxUSE_RICHEDIT': 0, + 'wxUSE_RICHEDIT2': 0, + 'wxUSE_OWNER_DRAWN': 1, + 'wxUSE_TASKBARICON_BALLOONS': 0, + 'wxUSE_UXTHEME': 1, + 'wxUSE_UXTHEME_AUTO': 1, + 'wxUSE_INKEDIT': 0, + 'wxUSE_INICONF': 0, + 'wxUSE_DATEPICKCTRL_GENERIC': 0, + 'wxUSE_CRASHREPORT': 1, + 'wxUSE_AUTOID_MANAGEMENT': 1, + 'wxUSE_FILE_HISTORY': 0, + 'wxUSE_UIACTIONSIMULATOR': 0, + 'wxUSE_CAIRO': 0, + 'wxUSE_COMMANDLINKBUTTON': 0, + 'wxUSE_RICHMSGDLG': 0, + 'wxUSE_STD_CONTAINERS': 1, + 'wxUSE_STD_STRING_CONV_IN_WXSTRING': 0, + 'wxUSE_ARTPROVIDER_STD': 0, + 'wxUSE_ARTPROVIDER_TANGO': 0, + 'wxUSE_DC_TRANSFORM_MATRIX': 0, + 'wxUSE_MARKUP': 0, + 'wxUSE_TREELISTCTRL': 1, + 'wxUSE_TIMEPICKCTRL': 1, + 'wxUSE_WEBVIEW': 0, + 'wxUSE_RICHTOOLTIP': 0, + 'wxUSE_COMPILER_TLS': 2, + 'wxUSE_PREFERENCES_EDITOR': 0, + 'wxUSE_STD_CONTAINERS_COMPATIBLY': 0, + 'wxUSE_TASKBARBUTTON': 0, + 'wxUSE_ADDREMOVECTRL': 0, + 'wxUSE_ACTIVITYINDICATOR': 0, + 'wxUSE_WINRT': 0, }) wx = cmake.subproject('wxWidgets', options: opt_var) deps += [ wx.dependency('wxmono'), - wx.dependency('wxregex'), - wx.dependency('wxscintilla') + wx.dependency('wxscintilla'), + # wx.dependency('wxpng'), + # wx.dependency('wxzlib'), + # wx.dependency('wxexpat'), ] - if host_machine.system() == 'windows' or host_machine.system() == 'darwin' - deps += [ - wx.dependency('wxpng'), - ] - endif - if host_machine.system() == 'windows' - deps += [ - wx.dependency('wxzlib'), - wx.dependency('wxexpat'), - ] - if cc.has_header('rpc.h') deps += cc.find_library('rpcrt4', required: true) else diff --git a/src/ass_attachment.cpp b/src/ass_attachment.cpp index bc9917c64b..d198039517 100644 --- a/src/ass_attachment.cpp +++ b/src/ass_attachment.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include @@ -32,7 +33,7 @@ AssAttachment::AssAttachment(std::string const& header, AssEntryGroup group) { } -AssAttachment::AssAttachment(agi::fs::path const& name, AssEntryGroup group) +AssAttachment::AssAttachment(std::filesystem::path const& name, AssEntryGroup group) : filename(name.filename().string()) , group(group) { @@ -43,8 +44,8 @@ AssAttachment::AssAttachment(agi::fs::path const& name, AssEntryGroup group) agi::read_file_mapping file(name); auto buff = file.read(); - entry_data = (group == AssEntryGroup::FONT ? "fontname: " : "filename: ") + filename.get() + "\r\n"; - entry_data = entry_data.get() + agi::ass::UUEncode(buff, buff + file.size()); + entry_data = agi::Str(group == AssEntryGroup::FONT ? "fontname: " : "filename: ", filename.get(), "\r\n", + agi::ass::UUEncode(buff, buff + file.size())); } size_t AssAttachment::GetSize() const { @@ -52,7 +53,7 @@ size_t AssAttachment::GetSize() const { return entry_data.get().size() - header_end - 1; } -void AssAttachment::Extract(agi::fs::path const& filename) const { +void AssAttachment::Extract(std::filesystem::path const& filename) const { auto header_end = entry_data.get().find('\n'); auto decoded = agi::ass::UUDecode(entry_data.get().c_str() + header_end + 1, &entry_data.get().back() + 1); agi::io::Save(filename, true).Get().write(&decoded[0], decoded.size()); diff --git a/src/ass_attachment.h b/src/ass_attachment.h index d99e0fbf70..81f1af224f 100644 --- a/src/ass_attachment.h +++ b/src/ass_attachment.h @@ -16,9 +16,8 @@ #include "ass_entry.h" -#include - #include +#include /// @class AssAttachment class AssAttachment final : public AssEntry { @@ -39,7 +38,7 @@ class AssAttachment final : public AssEntry { /// Extract the contents of this attachment to a file /// @param filename Path to save the attachment to - void Extract(agi::fs::path const& filename) const; + void Extract(std::filesystem::path const& filename) const; /// Get the name of the attached file /// @param raw If false, remove the SSA filename mangling @@ -50,5 +49,5 @@ class AssAttachment final : public AssEntry { AssAttachment(AssAttachment const& rgt) = default; AssAttachment(std::string const& header, AssEntryGroup group); - AssAttachment(agi::fs::path const& name, AssEntryGroup group); + AssAttachment(std::filesystem::path const& name, AssEntryGroup group); }; diff --git a/src/ass_dialogue.cpp b/src/ass_dialogue.cpp index 518e4d4fea..50c618d933 100644 --- a/src/ass_dialogue.cpp +++ b/src/ass_dialogue.cpp @@ -27,25 +27,20 @@ // // Aegisub Project http://www.aegisub.org/ -/// @file ass_dialogue.cpp -/// @brief Class for dialogue lines in subtitles -/// @ingroup subs_storage - #include "ass_dialogue.h" #include "subtitle_format.h" #include "utils.h" #include #include -#include +#include #include -#include -#include #include #include #include #include +#include using namespace boost::adaptors; @@ -72,31 +67,31 @@ AssDialogue::AssDialogue(std::string const& data) { AssDialogue::~AssDialogue () { } class tokenizer { - agi::StringRange str; - agi::split_iterator pos; + std::string_view str; + agi::split_iterator pos; public: - tokenizer(agi::StringRange const& str) : str(str) , pos(agi::Split(str, ',')) { } + tokenizer(std::string_view str) : str(str), pos(agi::Split(str, ',')) { } - agi::StringRange next_tok() { + std::string_view next_tok() { if (pos.eof()) - throw SubtitleFormatParseError("Failed parsing line: " + std::string(str.begin(), str.end())); + throw SubtitleFormatParseError(agi::Str("Failed parsing line: ", str)); return *pos++; } - std::string next_str() { return agi::str(next_tok()); } - std::string next_str_trim() { return agi::str(boost::trim_copy(next_tok())); } + std::string next_str() { return std::string(next_tok()); } + std::string next_str_trim() { return std::string(agi::Trim(next_tok())); } }; void AssDialogue::Parse(std::string const& raw) { - agi::StringRange str; + std::string_view str = raw; if (boost::starts_with(raw, "Dialogue:")) { Comment = false; - str = agi::StringRange(raw.begin() + 10, raw.end()); + str.remove_prefix(10); } else if (boost::starts_with(raw, "Comment:")) { Comment = true; - str = agi::StringRange(raw.begin() + 9, raw.end()); + str.remove_prefix(9); } else throw SubtitleFormatParseError("Failed parsing line: " + raw); @@ -108,20 +103,17 @@ void AssDialogue::Parse(std::string const& raw) { bool ssa = boost::istarts_with(tmp, "marked="); // Get layer number - if (ssa) - Layer = 0; - else - Layer = boost::lexical_cast(tmp); + Layer = ssa ? 0 : boost::lexical_cast(tmp); - Start = tkn.next_str_trim(); - End = tkn.next_str_trim(); + Start = agi::Trim(tkn.next_tok()); + End = agi::Trim(tkn.next_tok()); Style = tkn.next_str_trim(); Actor = tkn.next_str_trim(); for (int& margin : Margin) - margin = mid(-9999, boost::lexical_cast(tkn.next_str()), 99999); + margin = mid(-9999, boost::lexical_cast(tkn.next_tok()), 99999); Effect = tkn.next_str_trim(); - std::string text{tkn.next_tok().begin(), str.end()}; + std::string text{tkn.next_tok()}; if (text.size() > 1 && text[0] == '{' && text[1] == '=') { static const boost::regex extradata_test("^\\{(=\\d+)+\\}"); @@ -201,7 +193,7 @@ std::vector> AssDialogue::ParseTags() const { // Empty line, make an empty block if (Text.get().empty()) { - Blocks.push_back(agi::make_unique()); + Blocks.push_back(std::make_unique()); return Blocks; } @@ -226,11 +218,11 @@ std::vector> AssDialogue::ParseTags() const { if (work.size() && work.find('\\') == std::string::npos) { //We've found an override block with no backslashes //We're going to assume it's a comment and not consider it an override block - Blocks.push_back(agi::make_unique(work)); + Blocks.push_back(std::make_unique(work)); } else { // Create block - auto block = agi::make_unique(work); + auto block = std::make_unique(work); block->ParseTags(); // Look for \p in block @@ -258,9 +250,9 @@ std::vector> AssDialogue::ParseTags() const { } if (drawingLevel == 0) - Blocks.push_back(agi::make_unique(work)); + Blocks.push_back(std::make_unique(work)); else - Blocks.push_back(agi::make_unique(work, drawingLevel)); + Blocks.push_back(std::make_unique(work, drawingLevel)); } return Blocks; @@ -273,7 +265,7 @@ void AssDialogue::StripTags() { static std::string get_text(std::unique_ptr &d) { return d->GetText(); } void AssDialogue::UpdateText(std::vector>& blocks) { if (blocks.empty()) return; - Text = join(blocks | transformed(get_text), ""); + Text = agi::Join("", blocks | transformed(get_text)); } bool AssDialogue::CollidesWith(const AssDialogue *target) const { @@ -284,5 +276,5 @@ bool AssDialogue::CollidesWith(const AssDialogue *target) const { static std::string get_text_p(AssDialogueBlock *d) { return d->GetText(); } std::string AssDialogue::GetStrippedText() const { auto blocks = ParseTags(); - return join(blocks | agi::of_type() | transformed(get_text_p), ""); + return agi::Join("", blocks | agi::of_type() | transformed(get_text_p)); } diff --git a/src/ass_exporter.cpp b/src/ass_exporter.cpp index 123bcd076b..39b227706b 100644 --- a/src/ass_exporter.cpp +++ b/src/ass_exporter.cpp @@ -79,7 +79,7 @@ std::vector AssExporter::GetAllFilterNames() const { return names; } -void AssExporter::Export(agi::fs::path const& filename, std::string const& charset, wxWindow *export_dialog) { +void AssExporter::Export(std::filesystem::path const& filename, const char *charset, wxWindow *export_dialog) { AssFile subs(*c->ass); for (auto filter : filters) { diff --git a/src/ass_exporter.h b/src/ass_exporter.h index 1565c01f26..4dd8ea804d 100644 --- a/src/ass_exporter.h +++ b/src/ass_exporter.h @@ -27,13 +27,7 @@ // // Aegisub Project http://www.aegisub.org/ -/// @file ass_exporter.h -/// @see ass_exporter.cpp -/// @ingroup export -/// - -#include - +#include #include #include #include @@ -73,7 +67,7 @@ class AssExporter { /// @param file Target filename /// @param charset Target charset /// @param parent_window Parent window the filters should use when opening dialogs - void Export(agi::fs::path const& file, std::string const& charset, wxWindow *parent_window= nullptr); + void Export(std::filesystem::path const& file, const char *charset, wxWindow *parent_window= nullptr); /// Add configuration panels for all registered filters to the target sizer /// @param parent Parent window for controls diff --git a/src/ass_file.cpp b/src/ass_file.cpp index bd06c562e8..eb46454946 100644 --- a/src/ass_file.cpp +++ b/src/ass_file.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -98,7 +97,7 @@ EntryList::iterator AssFile::iterator_to(AssDialogue& line) { return in_list ? Events.iterator_to(line) : Events.end(); } -void AssFile::InsertAttachment(agi::fs::path const& filename) { +void AssFile::InsertAttachment(std::filesystem::path const& filename) { AssEntryGroup group = AssEntryGroup::GRAPHIC; auto ext = boost::to_lower_copy(filename.extension().string()); @@ -108,7 +107,7 @@ void AssFile::InsertAttachment(agi::fs::path const& filename) { Attachments.emplace_back(filename, group); } -std::string AssFile::GetScriptInfo(std::string const& key) const { +std::string_view AssFile::GetScriptInfo(std::string_view key) const { for (auto const& info : Info) { if (boost::iequals(key, info.Key())) return info.Value(); @@ -117,11 +116,11 @@ std::string AssFile::GetScriptInfo(std::string const& key) const { return ""; } -int AssFile::GetScriptInfoAsInt(std::string const& key) const { - return atoi(GetScriptInfo(key).c_str()); +int AssFile::GetScriptInfoAsInt(std::string_view key) const { + return atoi(GetScriptInfo(key).data()); } -void AssFile::SetScriptInfo(std::string const& key, std::string const& value) { +void AssFile::SetScriptInfo(std::string_view key, std::string_view value) { for (auto it = Info.begin(); it != Info.end(); ++it) { if (boost::iequals(key, it->Key())) { if (value.empty()) @@ -227,14 +226,14 @@ void AssFile::Sort(EntryList &lst, CompFunc comp, std::set #include +#include #include #include #include @@ -105,7 +105,7 @@ class AssFile { /// @param style_catalog Style catalog name to fill styles from, blank to use default style void LoadDefault(bool defline = true, std::string const& style_catalog = std::string()); /// Attach a file to the ass file - void InsertAttachment(agi::fs::path const& filename); + void InsertAttachment(std::filesystem::path const& filename); /// Get the names of all of the styles available std::vector GetStyles() const; /// @brief Get a style by name @@ -120,17 +120,17 @@ class AssFile { /// @param[in] h Height void GetResolution(int &w,int &h) const; /// Get the value in a [Script Info] key as int, or 0 if it is not present - int GetScriptInfoAsInt(std::string const& key) const; + int GetScriptInfoAsInt(std::string_view key) const; /// Get the value in a [Script Info] key as string. - std::string GetScriptInfo(std::string const& key) const; + std::string_view GetScriptInfo(std::string_view key) const; /// Set the value of a [Script Info] key. Adds it if it doesn't exist. - void SetScriptInfo(std::string const& key, std::string const& value); + void SetScriptInfo(std::string_view key, std::string_view value); /// @brief Add a new extradata entry /// @param key Class identifier/owner for the extradata /// @param value Data for the extradata /// @return ID of the created entry - uint32_t AddExtradata(std::string const& key, std::string const& value); + uint32_t AddExtradata(std::string_view key, std::string_view value); /// Fetch all extradata entries from a list of IDs std::vector GetExtradata(std::vector const& id_list) const; /// Remove unreferenced extradata entries diff --git a/src/ass_info.h b/src/ass_info.h index af6463d78a..8ca22273bb 100644 --- a/src/ass_info.h +++ b/src/ass_info.h @@ -22,12 +22,12 @@ class AssInfo final : public AssEntry { public: AssInfo(AssInfo const& o) = default; - AssInfo(std::string key, std::string value) : key(std::move(key)), value(std::move(value)) { } + AssInfo(std::string_view key, std::string_view value) : key(key), value(value) { } AssEntryGroup Group() const override { return AssEntryGroup::INFO; } std::string GetEntryData() const { return key + ": " + value; } - std::string Key() const { return key; } - std::string Value() const { return value; } - void SetValue(std::string const& new_value) { value = new_value; } + std::string_view Key() const { return key; } + std::string_view Value() const { return value; } + void SetValue(std::string_view new_value) { value = new_value; } }; diff --git a/src/ass_karaoke.cpp b/src/ass_karaoke.cpp index ee47115e7f..e89959a79e 100644 --- a/src/ass_karaoke.cpp +++ b/src/ass_karaoke.cpp @@ -18,76 +18,23 @@ #include "ass_dialogue.h" +#include #include #include #include -std::string AssKaraoke::Syllable::GetText(bool k_tag) const { - std::string ret; +using namespace agi::ass; - if (k_tag) - ret = agi::format("{%s%d}", tag_type, ((duration + 5) / 10)); +std::vector ParseKaraokeSyllables(const AssDialogue *line) { + std::vector syls; + if (!line) return syls; - size_t idx = 0; - for (auto const& ovr : ovr_tags) { - ret += text.substr(idx, ovr.first - idx); - ret += ovr.second; - idx = ovr.first; - } - ret += text.substr(idx); - return ret; -} - -AssKaraoke::AssKaraoke(const AssDialogue *line, bool auto_split, bool normalize) { - if (line) SetLine(line, auto_split, normalize); -} - -void AssKaraoke::SetLine(const AssDialogue *line, bool auto_split, bool normalize) { - syls.clear(); - Syllable syl; + KaraokeSyllable syl; syl.start_time = line->Start; syl.duration = 0; syl.tag_type = "\\k"; - ParseSyllables(line, syl); - - if (normalize) { - // Normalize the syllables so that the total duration is equal to the line length - int end_time = line->End; - int last_end = syl.start_time + syl.duration; - - // Total duration is shorter than the line length so just extend the last - // syllable; this has no effect on rendering but is easier to work with - if (last_end < end_time) - syls.back().duration += end_time - last_end; - else if (last_end > end_time) { - // Truncate any syllables that extend past the end of the line - for (auto& syl : syls) { - if (syl.start_time > end_time) { - syl.start_time = end_time; - syl.duration = 0; - } - else { - syl.duration = std::min(syl.duration, end_time - syl.start_time); - } - } - } - } - - // Add karaoke splits at each space - if (auto_split && syls.size() == 1) { - size_t pos; - no_announce = true; - while ((pos = syls.back().text.find(' ')) != std::string::npos) - AddSplit(syls.size() - 1, pos + 1); - no_announce = false; - } - - AnnounceSyllablesChanged(); -} - -void AssKaraoke::ParseSyllables(const AssDialogue *line, Syllable &syl) { for (auto& block : line->ParseTags()) { std::string text = block->GetText(); @@ -145,120 +92,10 @@ void AssKaraoke::ParseSyllables(const AssDialogue *line, Syllable &syl) { } syls.push_back(syl); + return syls; } -std::string AssKaraoke::GetText() const { - std::string text; - text.reserve(size() * 10); - - for (auto const& syl : syls) - text += syl.GetText(true); - - return text; -} - -std::string AssKaraoke::GetTagType() const { - return begin()->tag_type; -} - -void AssKaraoke::SetTagType(std::string const& new_type) { - for (auto& syl : syls) - syl.tag_type = new_type; -} - -void AssKaraoke::AddSplit(size_t syl_idx, size_t pos) { - syls.insert(syls.begin() + syl_idx + 1, Syllable()); - Syllable &syl = syls[syl_idx]; - Syllable &new_syl = syls[syl_idx + 1]; - - // If the syl is empty or the user is adding a syllable past the last - // character then pos will be out of bounds. Doing this is a bit goofy, - // but it's sometimes required for complex karaoke scripts - if (pos < syl.text.size()) { - new_syl.text = syl.text.substr(pos); - syl.text = syl.text.substr(0, pos); - } - - if (new_syl.text.empty()) - new_syl.duration = 0; - else if (syl.text.empty()) { - new_syl.duration = syl.duration; - syl.duration = 0; - } - else { - new_syl.duration = (syl.duration * new_syl.text.size() / (syl.text.size() + new_syl.text.size()) + 5) / 10 * 10; - syl.duration -= new_syl.duration; - } - - assert(syl.duration >= 0); - - new_syl.start_time = syl.start_time + syl.duration; - new_syl.tag_type = syl.tag_type; - - // Move all override tags after the split to the new syllable and fix the indices - size_t text_len = syl.text.size(); - for (auto it = syl.ovr_tags.begin(); it != syl.ovr_tags.end(); ) { - if (it->first < text_len) - ++it; - else { - new_syl.ovr_tags[it->first - text_len] = it->second; - syl.ovr_tags.erase(it++); - } - } - - if (!no_announce) AnnounceSyllablesChanged(); -} - -void AssKaraoke::RemoveSplit(size_t syl_idx) { - // Don't allow removing the first syllable - if (syl_idx == 0) return; - - Syllable &syl = syls[syl_idx]; - Syllable &prev = syls[syl_idx - 1]; - - prev.duration += syl.duration; - for (auto const& tag : syl.ovr_tags) - prev.ovr_tags[tag.first + prev.text.size()] = tag.second; - prev.text += syl.text; - - syls.erase(syls.begin() + syl_idx); - - if (!no_announce) AnnounceSyllablesChanged(); +void SetKaraokeLine(Karaoke& karaoke, const AssDialogue *line, bool auto_split, bool normalize) { + karaoke.SetLine(ParseKaraokeSyllables(line), normalize, auto_split); } -void AssKaraoke::SetStartTime(size_t syl_idx, int time) { - // Don't allow moving the first syllable - if (syl_idx == 0) return; - - Syllable &syl = syls[syl_idx]; - Syllable &prev = syls[syl_idx - 1]; - - assert(time >= prev.start_time); - assert(time <= syl.start_time + syl.duration); - - int delta = time - syl.start_time; - syl.start_time = time; - syl.duration -= delta; - prev.duration += delta; -} - -void AssKaraoke::SetLineTimes(int start_time, int end_time) { - assert(end_time >= start_time); - - size_t idx = 0; - // Chop off any portion of syllables starting before the new start_time - do { - int delta = start_time - syls[idx].start_time; - syls[idx].start_time = start_time; - syls[idx].duration = std::max(0, syls[idx].duration - delta); - } while (++idx < syls.size() && syls[idx].start_time < start_time); - - // And truncate any syllables ending after the new end_time - idx = syls.size() - 1; - while (syls[idx].start_time > end_time) { - syls[idx].start_time = end_time; - syls[idx].duration = 0; - --idx; - } - syls[idx].duration = end_time - syls[idx].start_time; -} diff --git a/src/ass_karaoke.h b/src/ass_karaoke.h index 9802957fa7..b524a66828 100644 --- a/src/ass_karaoke.h +++ b/src/ass_karaoke.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011, Thomas Goyne +// Copyright (c) 2022, Thomas Goyne // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -14,73 +14,10 @@ // // Aegisub Project http://www.aegisub.org/ -#include -#include #include -#include - -namespace agi { struct Context; } +namespace agi::ass { class Karaoke; struct KaraokeSyllable; } class AssDialogue; -/// @class AssKaraoke -/// @brief Karaoke parser and parsed karaoke data model -class AssKaraoke { -public: - /// Parsed syllable data - struct Syllable { - int start_time; ///< Start time relative to time zero (not line start) in milliseconds - int duration; ///< Duration in milliseconds - std::string text; ///< Stripped syllable text - std::string tag_type; ///< \k, \kf or \ko - /// Non-karaoke override tags in this syllable. Key is an index in text - /// before which the value should be inserted - std::map ovr_tags; - - /// Get the text of this line with override tags and optionally the karaoke tag - std::string GetText(bool k_tag) const; - }; -private: - std::vector syls; - - bool no_announce = false; - - agi::signal::Signal<> AnnounceSyllablesChanged; - void ParseSyllables(const AssDialogue *line, Syllable &syl); - -public: - /// Constructor - /// @param line Initial line - /// @param auto_split Should the line automatically be split on spaces if there are no k tags? - /// @param normalize Should the total duration of the syllables be forced to equal the line duration? - AssKaraoke(const AssDialogue *line = nullptr, bool auto_split = false, bool normalize = true); - - /// Parse a dialogue line - void SetLine(const AssDialogue *line, bool auto_split = false, bool normalize = true); - - /// Add a split before character pos in syllable syl_idx - void AddSplit(size_t syl_idx, size_t pos); - /// Remove the split at the given index - void RemoveSplit(size_t syl_idx); - /// Set the start time of a syllable in ms - void SetStartTime(size_t syl_idx, int time); - /// Adjust the line's start and end times without shifting the syllables - void SetLineTimes(int start_time, int end_time); - - typedef std::vector::const_iterator iterator; - - iterator begin() const { return syls.begin(); } - iterator end() const { return syls.end(); } - size_t size() const { return syls.size(); } - - /// Get the line's text with k tags - std::string GetText() const; - - /// Get the karaoke tag type used, with leading slash - /// @returns "\k", "\kf", or "\ko" - std::string GetTagType() const; - /// Set the tag type for all karaoke tags in this line - void SetTagType(std::string const& new_type); - - DEFINE_SIGNAL_ADDERS(AnnounceSyllablesChanged, AddSyllablesChangedListener) -}; +std::vector ParseKaraokeSyllables(const AssDialogue *line); +void SetKaraokeLine(agi::ass::Karaoke& karaoke, const AssDialogue *line, bool auto_split = false, bool normalize = true); diff --git a/src/ass_override.cpp b/src/ass_override.cpp index fc8758c089..9fec83f4f1 100644 --- a/src/ass_override.cpp +++ b/src/ass_override.cpp @@ -35,11 +35,11 @@ #include #include #include -#include +#include +#include #include #include -#include #include #include #include @@ -85,12 +85,12 @@ template<> bool AssOverrideParameter::Get() const { } template<> agi::Color AssOverrideParameter::Get() const { - return Get(); + return std::string_view(Get()); } template<> AssDialogueBlockOverride *AssOverrideParameter::Get() const { if (!block) { - block = agi::make_unique(Get()); + block = std::make_unique(Get()); block->ParseTags(); } return block.get(); @@ -307,7 +307,7 @@ static void load_protos() { proto[i].AddParam(VariableDataType::BLOCK); } -std::vector tokenize(const std::string &text) { +std::vector tokenize(std::string_view text) { std::vector paramList; paramList.reserve(6); @@ -317,7 +317,7 @@ std::vector tokenize(const std::string &text) { if (text[0] != '(') { // There's just one parameter (because there's no parentheses) // This means text is all our parameters - paramList.emplace_back(boost::trim_copy(text)); + paramList.emplace_back(agi::Trim(text)); return paramList; } @@ -344,7 +344,7 @@ std::vector tokenize(const std::string &text) { i++; } // i now points to the first character not member of this parameter - paramList.emplace_back(boost::trim_copy(text.substr(start, i - start))); + paramList.emplace_back(agi::Trim(text.substr(start, i - start))); } if (i+1 < textlen) { @@ -355,7 +355,7 @@ std::vector tokenize(const std::string &text) { return paramList; } -void parse_parameters(AssOverrideTag *tag, const std::string &text, AssOverrideTagProto::iterator proto_it) { +void parse_parameters(AssOverrideTag *tag, std::string_view text, AssOverrideTagProto::iterator proto_it) { tag->Clear(); // Tokenize text, attempting to find all parameters @@ -412,7 +412,7 @@ void AssDialogueBlockOverride::AddTag(std::string const& tag) { static std::string tag_str(AssOverrideTag const& t) { return t; } std::string AssDialogueBlockOverride::GetText() { - text = "{" + join(Tags | transformed(tag_str), std::string()) + "}"; + text = agi::Str("{", agi::Join("", Tags | transformed(tag_str)), "}"); return text; } diff --git a/src/ass_parser.cpp b/src/ass_parser.cpp index f55fc3567f..ebbd6211b2 100644 --- a/src/ass_parser.cpp +++ b/src/ass_parser.cpp @@ -23,7 +23,6 @@ #include "subtitle_format.h" #include -#include #include #include @@ -32,11 +31,11 @@ #include #include #include -#include #include +#include class AssParser::HeaderToProperty { - using field = boost::variant< + using field = std::variant< std::string ProjectProperties::*, int ProjectProperties::*, double ProjectProperties::* @@ -81,7 +80,7 @@ class AssParser::HeaderToProperty { void operator()(int ProjectProperties::*f) const { try_parse(value, &(obj.*f)); } void operator()(double ProjectProperties::*f) const { try_parse(value, &(obj.*f)); } } visitor {target->Properties, value}; - boost::apply_visitor(visitor, it->second); + std::visit(visitor, it->second); return true; } @@ -95,15 +94,14 @@ class AssParser::HeaderToProperty { }; AssParser::AssParser(AssFile *target, int version) -: property_handler(agi::make_unique()) +: property_handler(std::make_unique()) , target(target) , version(version) , state(&AssParser::ParseScriptInfoLine) { } -AssParser::~AssParser() { -} +AssParser::~AssParser() = default; void AssParser::ParseAttachmentLine(std::string const& data) { bool is_filename = boost::starts_with(data, "fontname: ") || boost::starts_with(data, "filename: "); @@ -188,12 +186,12 @@ void AssParser::ParseStyleLine(std::string const& data) { void AssParser::ParseFontLine(std::string const& data) { if (boost::starts_with(data, "fontname: ")) - attach = agi::make_unique(data, AssEntryGroup::FONT); + attach = std::make_unique(data, AssEntryGroup::FONT); } void AssParser::ParseGraphicsLine(std::string const& data) { if (boost::starts_with(data, "filename: ")) - attach = agi::make_unique(data, AssEntryGroup::GRAPHIC); + attach = std::make_unique(data, AssEntryGroup::GRAPHIC); } void AssParser::ParseExtradataLine(std::string const &data) { diff --git a/src/ass_style.cpp b/src/ass_style.cpp index 0b778b328f..fcdbbdaa5d 100644 --- a/src/ass_style.cpp +++ b/src/ass_style.cpp @@ -40,7 +40,6 @@ #include #include -#include #include #include @@ -54,19 +53,20 @@ AssEntryGroup AssStyle::Group() const { return AssEntryGroup::STYLE; } namespace { class parser { - agi::split_iterator pos; + agi::split_iterator pos; - std::string next_tok() { + std::string_view next_tok() { if (pos.eof()) throw SubtitleFormatParseError("Malformed style: not enough fields"); - return agi::str(trim_copy(*pos++)); + return agi::Trim(*pos++); } public: - parser(std::string const& str) { - auto colon = find(str.begin(), str.end(), ':'); - if (colon != str.end()) - pos = agi::Split(agi::StringRange(colon + 1, str.end()), ','); + parser(std::string_view str) { + if (auto colon = str.find(':'); colon != str.npos) { + str.remove_prefix(colon); + pos = agi::Split(str, ','); + } } void check_done() const { @@ -74,7 +74,7 @@ class parser { throw SubtitleFormatParseError("Malformed style: too many fields"); } - std::string next_str() { return next_tok(); } + std::string next_str() { return std::string(next_tok()); } agi::Color next_color() { return next_tok(); } int next_int() { @@ -102,7 +102,7 @@ class parser { }; } -AssStyle::AssStyle(std::string const& str, int version) { +AssStyle::AssStyle(std::string_view str, int version) { parser p(str); name = p.next_str(); diff --git a/src/ass_style.h b/src/ass_style.h index d366880d4e..60e028bb9c 100644 --- a/src/ass_style.h +++ b/src/ass_style.h @@ -71,7 +71,7 @@ class AssStyle final : public AssEntry, public AssEntryListHook { static void GetEncodings(wxArrayString &encodingStrings); AssStyle(); - AssStyle(std::string const& data, int version=1); + AssStyle(std::string_view data, int version=1); std::string const& GetEntryData() const { return data; } AssEntryGroup Group() const override; diff --git a/src/ass_style_storage.cpp b/src/ass_style_storage.cpp index d6dc6d8354..0cdcea462d 100644 --- a/src/ass_style_storage.cpp +++ b/src/ass_style_storage.cpp @@ -27,11 +27,6 @@ // // Aegisub Project http://www.aegisub.org/ -/// @file ass_style_storage.cpp -/// @brief Manage stores of styles -/// @ingroup style_editor -/// - #include "ass_style_storage.h" #include "ass_file.h" @@ -41,8 +36,8 @@ #include #include #include -#include #include +#include #include @@ -56,13 +51,13 @@ void AssStyleStorage::Save() const { agi::fs::CreateDirectory(file.parent_path()); agi::io::Save out(file); - out.Get() << "\xEF\xBB\xBF"; + out.Get() << "\xEF\xBB\xBF"; // UTF-8 BOM for (auto const& cur : style) - out.Get() << cur->GetEntryData() << std::endl; + out.Get() << cur->GetEntryData() << "\n"; } -void AssStyleStorage::Load(agi::fs::path const& filename) { +void AssStyleStorage::Load(std::filesystem::path const& filename) { file = filename; clear(); @@ -70,7 +65,7 @@ void AssStyleStorage::Load(agi::fs::path const& filename) { auto in = agi::io::Open(file); for (auto const& line : agi::line_iterator(*in)) { try { - style.emplace_back(agi::make_unique(line)); + style.emplace_back(std::make_unique(line)); } catch(...) { /* just ignore invalid lines for now */ } @@ -81,9 +76,8 @@ void AssStyleStorage::Load(agi::fs::path const& filename) { } } -void AssStyleStorage::LoadCatalog(std::string const& catalogname) { - auto filename = config::path->Decode("?user/catalog/" + catalogname + ".sty"); - Load(filename); +void AssStyleStorage::LoadCatalog(std::string_view catalogname) { + Load(config::path->Decode(agi::Str("?user/catalog/", catalogname, ".sty"))); } void AssStyleStorage::Delete(int idx) { @@ -97,7 +91,7 @@ std::vector AssStyleStorage::GetNames() { return names; } -AssStyle *AssStyleStorage::GetStyle(std::string const& name) { +AssStyle *AssStyleStorage::GetStyle(std::string_view name) { for (auto& cur : style) { if (boost::iequals(cur->name, name)) return cur.get(); @@ -108,13 +102,13 @@ AssStyle *AssStyleStorage::GetStyle(std::string const& name) { std::vector AssStyleStorage::GetCatalogs() { std::vector catalogs; for (auto const& file : agi::fs::DirectoryIterator(config::path->Decode("?user/catalog/"), "*.sty")) - catalogs.push_back(agi::fs::path(file).stem().string()); + catalogs.push_back(std::filesystem::path(file).stem().string()); return catalogs; } -bool AssStyleStorage::CatalogExists(std::string const& catalogname) { +bool AssStyleStorage::CatalogExists(std::string_view catalogname) { if (catalogname.empty()) return false; - auto filename = config::path->Decode("?user/catalog/" + catalogname + ".sty"); + auto filename = config::path->Decode(agi::Str("?user/catalog/", catalogname, ".sty")); return agi::fs::FileExists(filename); } diff --git a/src/ass_style_storage.h b/src/ass_style_storage.h index 37f396c6ff..6b1f7cb8e1 100644 --- a/src/ass_style_storage.h +++ b/src/ass_style_storage.h @@ -27,14 +27,7 @@ // // Aegisub Project http://www.aegisub.org/ -/// @file ass_style_storage.h -/// @see ass_style_storage.cpp -/// @ingroup style_editor -/// - -#include - -#include +#include #include #include #include @@ -43,7 +36,7 @@ class AssFile; class AssStyle; class AssStyleStorage { - agi::fs::path file; + std::filesystem::path file; std::vector> style; public: @@ -70,25 +63,25 @@ class AssStyleStorage { /// Get the style with the given name /// @param name Case-insensitive style name /// @return Style or nullptr if the requested style is not found - AssStyle *GetStyle(std::string const& name); + AssStyle *GetStyle(std::string_view name); /// Save stored styles to a file void Save() const; /// Load stored styles from a file /// @param filename Catalog filename. Does not have to exist. - void Load(agi::fs::path const& filename); + void Load(std::filesystem::path const& filename); /// Load stored styles from a file in the default location /// @param catalogname Basename for the catalog file. Does not have to exist. - void LoadCatalog(std::string const& catalogname); + void LoadCatalog(std::string_view catalogname); /// Make a list of all existing style catalogs in the default location static std::vector GetCatalogs(); /// Check whether the name catalog exists in the default location /// @param catalogname Basename for the catalog file to check for. - static bool CatalogExists(std::string const& catalogname); + static bool CatalogExists(std::string_view catalogname); /// Insert all styles into a file, replacing existing styles with the same names /// @param file File to replace styles in diff --git a/src/async_video_provider.cpp b/src/async_video_provider.cpp index 33dbe4bca1..2deb5972ee 100644 --- a/src/async_video_provider.cpp +++ b/src/async_video_provider.cpp @@ -91,7 +91,7 @@ static std::unique_ptr get_subs_provider(wxEvtHandler *evt_ha } } -AsyncVideoProvider::AsyncVideoProvider(agi::fs::path const& video_filename, std::string const& colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br) +AsyncVideoProvider::AsyncVideoProvider(std::filesystem::path const& video_filename, std::string_view colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br) : worker(agi::dispatch::Create()) , subs_provider(get_subs_provider(parent, br)) , source_provider(VideoProviderFactory::GetProvider(video_filename, colormatrix, br)) @@ -192,7 +192,7 @@ void AsyncVideoProvider::ProcAsync(uint_fast32_t req_version, bool check_updated last_rendered = frame_number; try { - FrameReadyEvent *evt = new FrameReadyEvent(ProcFrame(frame_number, time), time); + auto evt = new FrameReadyEvent(ProcFrame(frame_number, time), time); evt->SetEventType(EVT_FRAME_READY); parent->QueueEvent(evt); } @@ -208,8 +208,10 @@ std::shared_ptr AsyncVideoProvider::GetFrame(int frame, double time, return ret; } -void AsyncVideoProvider::SetColorSpace(std::string const& matrix) { - worker->Async([=] { source_provider->SetColorSpace(matrix); }); +void AsyncVideoProvider::SetColorSpace(std::string_view matrix) { + worker->Async([this, matrix = std::string(matrix)]() { + source_provider->SetColorSpace(matrix); + }); } wxDEFINE_EVENT(EVT_FRAME_READY, FrameReadyEvent); @@ -217,12 +219,12 @@ wxDEFINE_EVENT(EVT_VIDEO_ERROR, VideoProviderErrorEvent); wxDEFINE_EVENT(EVT_SUBTITLES_ERROR, SubtitlesProviderErrorEvent); VideoProviderErrorEvent::VideoProviderErrorEvent(VideoProviderError const& err) -: agi::Exception(err.GetMessage()) +: agi::Exception(std::string(err.GetMessage())) { SetEventType(EVT_VIDEO_ERROR); } SubtitlesProviderErrorEvent::SubtitlesProviderErrorEvent(std::string const& err) -: agi::Exception(err) +: agi::Exception(std::string(err)) { SetEventType(EVT_SUBTITLES_ERROR); } diff --git a/src/async_video_provider.h b/src/async_video_provider.h index ce5c83dbc4..8d06f26dd0 100644 --- a/src/async_video_provider.h +++ b/src/async_video_provider.h @@ -17,9 +17,9 @@ #include "include/aegisub/video_provider.h" #include -#include #include +#include #include #include #include @@ -109,7 +109,7 @@ class AsyncVideoProvider { std::shared_ptr GetFrame(int frame, double time, bool raw = false); /// Ask the video provider to change YCbCr matricies - void SetColorSpace(std::string const& matrix); + void SetColorSpace(std::string_view matrix); int GetFrameCount() const { return source_provider->GetFrameCount(); } int GetWidth() const { return source_provider->GetWidth(); } @@ -127,7 +127,7 @@ class AsyncVideoProvider { /// @brief Constructor /// @param videoFileName File to open /// @param parent Event handler to send FrameReady events to - AsyncVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br); + AsyncVideoProvider(std::filesystem::path const& filename, std::string_view colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br); ~AsyncVideoProvider(); }; diff --git a/src/audio_colorscheme.cpp b/src/audio_colorscheme.cpp index c918ffb973..1b19efb34e 100644 --- a/src/audio_colorscheme.cpp +++ b/src/audio_colorscheme.cpp @@ -34,12 +34,13 @@ #include "options.h" #include +#include AudioColorScheme::AudioColorScheme(int prec, std::string const& scheme_name, int audio_rendering_style) : palette((3<(audio_rendering_style)) { case AudioStyle_Normal: opt_base += "Normal/"; break; diff --git a/src/audio_display.cpp b/src/audio_display.cpp index 2041715fd6..44ea1ab5c8 100644 --- a/src/audio_display.cpp +++ b/src/audio_display.cpp @@ -46,7 +46,6 @@ #include #include -#include #include @@ -573,10 +572,10 @@ AudioDisplay::AudioDisplay(wxWindow *parent, AudioController *controller, agi::C : wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS|wxBORDER_SIMPLE) , audio_open_connection(context->project->AddAudioProviderListener(&AudioDisplay::OnAudioOpen, this)) , context(context) -, audio_renderer(agi::make_unique()) +, audio_renderer(std::make_unique()) , controller(controller) -, scrollbar(agi::make_unique(this)) -, timeline(agi::make_unique(this)) +, scrollbar(std::make_unique(this)) +, timeline(std::make_unique(this)) , style_ranges({{0, 0}}) { audio_renderer->SetAmplitudeScale(scale_amplitude); @@ -743,7 +742,7 @@ void AudioDisplay::ReloadRenderingSettings() if (OPT_GET("Audio/Spectrum")->GetBool()) { colour_scheme_name = OPT_GET("Colour/Audio Display/Spectrum")->GetString(); - auto audio_spectrum_renderer = agi::make_unique(colour_scheme_name); + auto audio_spectrum_renderer = std::make_unique(colour_scheme_name); int64_t spectrum_quality = OPT_GET("Audio/Renderer/Spectrum/Quality")->GetInt(); #ifdef WITH_FFTW3 @@ -765,7 +764,7 @@ void AudioDisplay::ReloadRenderingSettings() else { colour_scheme_name = OPT_GET("Colour/Audio Display/Waveform")->GetString(); - audio_renderer_provider = agi::make_unique(colour_scheme_name); + audio_renderer_provider = std::make_unique(colour_scheme_name); } audio_renderer->SetRenderer(audio_renderer_provider.get()); @@ -1106,7 +1105,7 @@ void AudioDisplay::OnMouseEvent(wxMouseEvent& event) if (markers.size()) { RemoveTrackCursor(); - audio_marker = agi::make_unique(markers, timing, this, (wxMouseButton)event.GetButton()); + audio_marker = std::make_unique(markers, timing, this, (wxMouseButton)event.GetButton()); SetDraggedObject(audio_marker.get()); return; } diff --git a/src/audio_karaoke.cpp b/src/audio_karaoke.cpp index 2abeb79d80..3f871d5b16 100644 --- a/src/audio_karaoke.cpp +++ b/src/audio_karaoke.cpp @@ -14,11 +14,6 @@ // // Aegisub Project http://www.aegisub.org/ -/// @file audio_karaoke.cpp -/// @brief Karaoke table UI in audio box (not in audio display) -/// @ingroup audio_ui -/// - #include "audio_karaoke.h" #include "include/aegisub/context.h" @@ -36,10 +31,10 @@ #include "selection_controller.h" #include "utils.h" -#include +#include +#include #include -#include #include #include #include @@ -64,7 +59,7 @@ AudioKaraoke::AudioKaraoke(wxWindow *parent, agi::Context *c) , file_changed(c->ass->AddCommitListener(&AudioKaraoke::OnFileChanged, this)) , audio_opened(c->project->AddAudioProviderListener(&AudioKaraoke::OnAudioOpened, this)) , active_line_changed(c->selectionController->AddActiveLineListener(&AudioKaraoke::OnActiveLineChanged, this)) -, kara(agi::make_unique()) +, kara(std::make_unique()) { using std::bind; @@ -227,7 +222,7 @@ void AudioKaraoke::RenderText() { dc.DrawLine(syl_line, 0, syl_line, bmp_size.GetHeight()); } -void AudioKaraoke::AddMenuItem(wxMenu &menu, std::string const& tag, wxString const& help, std::string const& selected) { +void AudioKaraoke::AddMenuItem(wxMenu &menu, std::string_view tag, wxString const& help, std::string_view selected) { wxMenuItem *item = menu.AppendCheckItem(-1, to_wx(tag), help); menu.Bind(wxEVT_MENU, std::bind(&AudioKaraoke::SetTagType, this, tag), item->GetId()); item->Check(tag == selected); @@ -237,7 +232,7 @@ void AudioKaraoke::OnContextMenu(wxContextMenuEvent&) { if (!enabled) return; wxMenu context_menu(_("Karaoke tag")); - std::string type = kara->GetTagType(); + auto type = kara->GetTagType(); AddMenuItem(context_menu, "\\k", _("Change karaoke tag to \\k"), type); AddMenuItem(context_menu, "\\kf", _("Change karaoke tag to \\kf"), type); @@ -335,26 +330,23 @@ void AudioKaraoke::OnScrollTimer(wxTimerEvent&) { void AudioKaraoke::LoadFromLine() { scroll_x = 0; scroll_timer.Stop(); - kara->SetLine(active_line, true); + SetKaraokeLine(*kara, active_line, true); SetDisplayText(); accept_button->Enable(kara->GetText() != active_line->Text); cancel_button->Enable(false); } void AudioKaraoke::SetDisplayText() { - using namespace boost::locale::boundary; - wxMemoryDC dc; dc.SetFont(split_font); - auto get_char_width = [&](std::string const& character) -> int { - const auto it = char_widths.find(character); - if (it != end(char_widths)) + auto get_char_width = [&](std::string_view character) -> int { + if (auto it = char_widths.find(character); it != char_widths.end()) return it->second; const auto size = dc.GetTextExtent(to_wx(character)); char_height = std::max(char_height, size.GetHeight()); - char_widths[character] = size.GetWidth(); + char_widths.emplace(character, size.GetWidth()); return size.GetWidth(); }; @@ -381,10 +373,11 @@ void AudioKaraoke::SetDisplayText() { char_to_byte.push_back(1); size_t syl_idx = 1; - const ssegment_index characters(character, begin(syl.text), end(syl.text)); - for (auto chr : characters) { + agi::BreakIterator characters; + characters.set_text(syl.text); + for (; !characters.done(); characters.next()) { // Calculate the width in pixels of this character - const std::string character = chr.str(); + const auto character = characters.current(); const int width = get_char_width(character); char_width = std::max(char_width, width); str_char_widths.push_back(width); @@ -425,7 +418,7 @@ void AudioKaraoke::AcceptSplit() { cancel_button->Enable(false); } -void AudioKaraoke::SetTagType(std::string const& new_tag) { +void AudioKaraoke::SetTagType(std::string_view new_tag) { kara->SetTagType(new_tag); AcceptSplit(); } diff --git a/src/audio_karaoke.h b/src/audio_karaoke.h index 520f486825..83682cf833 100644 --- a/src/audio_karaoke.h +++ b/src/audio_karaoke.h @@ -25,11 +25,17 @@ #include class AssDialogue; -class AssKaraoke; class wxButton; +namespace agi::ass { class Karaoke; } namespace agi { class AudioProvider; } namespace agi { struct Context; } +struct StringHash { + using is_transparent = void; + size_t operator()(std::string_view str) const { return std::hash{}(str); } + size_t operator()(std::string const& str) const { return (*this)(std::string_view(str)); } +}; + /// @class AudioKaraoke /// @brief Syllable split and join UI for karaoke /// @@ -71,7 +77,7 @@ class AudioKaraoke final : public wxWindow { /// Currently active dialogue line AssDialogue *active_line = nullptr; /// Karaoke data - std::unique_ptr kara; + std::unique_ptr kara; /// Current line's stripped text with spaces added between each syllable std::vector spaced_text; @@ -80,7 +86,7 @@ class AudioKaraoke final : public wxWindow { wxBitmap rendered_line; /// Indexes in spaced_text which are the beginning of syllables - std::vector syl_start_points; + std::vector syl_start_points; /// x coordinate in pixels of the separator lines of each syllable std::vector syl_lines; @@ -92,7 +98,7 @@ class AudioKaraoke final : public wxWindow { std::vector char_to_byte; /// Cached width of characters from GetTextExtent - std::unordered_map char_widths; + std::unordered_map> char_widths; int scroll_x = 0; ///< Distance the display has been shifted to the left in pixels int scroll_dir = 0; ///< Direction the display will be scrolled on scroll_timer ticks (+/- 1) @@ -118,9 +124,9 @@ class AudioKaraoke final : public wxWindow { void SetDisplayText(); /// Helper function for context menu creation - void AddMenuItem(wxMenu &menu, std::string const& tag, wxString const& help, std::string const& selected); + void AddMenuItem(wxMenu &menu, std::string_view tag, wxString const& help, std::string_view selected); /// Set the karaoke tags for the selected syllables to the indicated one - void SetTagType(std::string const& new_type); + void SetTagType(std::string_view new_type); /// Prerender the current line along with syllable split lines void RenderText(); diff --git a/src/audio_marker.cpp b/src/audio_marker.cpp index c627d5b964..fe584891b8 100644 --- a/src/audio_marker.cpp +++ b/src/audio_marker.cpp @@ -27,8 +27,6 @@ #include "project.h" #include "video_controller.h" -#include - #include class AudioMarkerKeyframe final : public AudioMarker { @@ -48,7 +46,7 @@ AudioMarkerProviderKeyframes::AudioMarkerProviderKeyframes(agi::Context *c, cons , timecode_slot(p->AddTimecodesListener(&AudioMarkerProviderKeyframes::Update, this)) , enabled_slot(OPT_SUB(opt_name, &AudioMarkerProviderKeyframes::Update, this)) , enabled_opt(OPT_GET(opt_name)) -, style(agi::make_unique("Colour/Audio Display/Keyframe")) +, style(std::make_unique("Colour/Audio Display/Keyframe")) { Update(); } @@ -115,7 +113,7 @@ void VideoPositionMarkerProvider::Update(int frame_number) { void VideoPositionMarkerProvider::OptChanged(agi::OptionValue const& opt) { if (opt.GetBool()) { video_seek_slot.Unblock(); - marker = agi::make_unique(); + marker = std::make_unique(); marker->SetPosition(vc->GetFrameN()); } else { @@ -130,7 +128,7 @@ void VideoPositionMarkerProvider::GetMarkers(const TimeRange &range, AudioMarker } SecondsMarkerProvider::SecondsMarkerProvider() -: pen(agi::make_unique("Colour/Audio Display/Seconds Line", 1, wxPENSTYLE_DOT)) +: pen(std::make_unique("Colour/Audio Display/Seconds Line", 1, wxPENSTYLE_DOT)) , enabled(OPT_GET("Audio/Display/Draw/Seconds")) , enabled_opt_changed(OPT_SUB("Audio/Display/Draw/Seconds", &SecondsMarkerProvider::EnabledOptChanged, this)) { diff --git a/src/audio_player.cpp b/src/audio_player.cpp index f5a8327ca0..68dac2149a 100644 --- a/src/audio_player.cpp +++ b/src/audio_player.cpp @@ -27,18 +27,13 @@ // // Aegisub Project http://www.aegisub.org/ -/// @file audio_player.cpp -/// @brief Baseclass for audio players -/// @ingroup audio_output -/// - #include "include/aegisub/audio_player.h" #include "audio_controller.h" #include "factory_manager.h" #include "options.h" -#include +#include std::unique_ptr CreateAlsaPlayer(agi::AudioProvider *providers, wxWindow *window); std::unique_ptr CreateDirectSoundPlayer(agi::AudioProvider *providers, wxWindow *window); @@ -55,7 +50,7 @@ namespace { bool hidden; }; - const factory factories[] = { + const std::initializer_list factories = { #ifdef WITH_ALSA {"ALSA", CreateAlsaPlayer, false}, #endif @@ -79,15 +74,15 @@ namespace { } std::vector AudioPlayerFactory::GetClasses() { - return ::GetClasses(boost::make_iterator_range(std::begin(factories), std::end(factories))); + return ::GetClasses(factories); } std::unique_ptr AudioPlayerFactory::GetAudioPlayer(agi::AudioProvider *provider, wxWindow *window) { - if (std::begin(factories) == std::end(factories)) + if (factories.size() == 0) throw AudioPlayerOpenError("No audio players are available."); auto preferred = OPT_GET("Audio/Player")->GetString(); - auto sorted = GetSorted(boost::make_iterator_range(std::begin(factories), std::end(factories)), preferred); + auto sorted = GetSorted(factories, preferred); std::string error; for (auto factory : sorted) { @@ -95,7 +90,7 @@ std::unique_ptr AudioPlayerFactory::GetAudioPlayer(agi::AudioProvid return factory->create(provider, window); } catch (AudioPlayerOpenError const& err) { - error += std::string(factory->name) + " factory: " + err.GetMessage() + "\n"; + agi::AppendStr(error, factory->name, " factory: ", err.GetMessage(), "\n"); } } throw AudioPlayerOpenError(error); diff --git a/src/audio_player_alsa.cpp b/src/audio_player_alsa.cpp index 5a17056229..0d5bcc3e23 100644 --- a/src/audio_player_alsa.cpp +++ b/src/audio_player_alsa.cpp @@ -42,7 +42,6 @@ #include #include -#include #include #include @@ -349,7 +348,7 @@ int64_t AlsaPlayer::GetCurrentPosition() std::unique_ptr CreateAlsaPlayer(agi::AudioProvider *provider, wxWindow *) { - return agi::make_unique(provider); + return std::make_unique(provider); } #endif // WITH_ALSA diff --git a/src/audio_player_dsound.cpp b/src/audio_player_dsound.cpp index 01b47b3544..104ff69cf7 100644 --- a/src/audio_player_dsound.cpp +++ b/src/audio_player_dsound.cpp @@ -41,7 +41,6 @@ #include #include -#include #include #include @@ -368,7 +367,7 @@ void DirectSoundPlayerThread::Stop() { } std::unique_ptr CreateDirectSoundPlayer(agi::AudioProvider *provider, wxWindow *parent) { - return agi::make_unique(provider, parent); + return std::make_unique(provider, parent); } #endif // WITH_DIRECTSOUND diff --git a/src/audio_player_dsound2.cpp b/src/audio_player_dsound2.cpp index dd7bf86800..1d2e847a86 100644 --- a/src/audio_player_dsound2.cpp +++ b/src/audio_player_dsound2.cpp @@ -43,7 +43,6 @@ #include #include #include -#include #include #include @@ -814,7 +813,7 @@ DirectSoundPlayer2::DirectSoundPlayer2(agi::AudioProvider *provider, wxWindow *p try { - thread = agi::make_unique(provider, WantedLatency, BufferLength, parent); + thread = std::make_unique(provider, WantedLatency, BufferLength, parent); } catch (const char *msg) { @@ -929,7 +928,7 @@ void DirectSoundPlayer2::SetVolume(double vol) } std::unique_ptr CreateDirectSound2Player(agi::AudioProvider *provider, wxWindow *parent) { - return agi::make_unique(provider, parent); + return std::make_unique(provider, parent); } #endif // WITH_DIRECTSOUND diff --git a/src/audio_player_openal.cpp b/src/audio_player_openal.cpp index b0f8372bdc..ca39dcb779 100644 --- a/src/audio_player_openal.cpp +++ b/src/audio_player_openal.cpp @@ -40,7 +40,6 @@ #include #include -#include #ifdef __WINDOWS__ #include @@ -305,7 +304,7 @@ int64_t OpenALPlayer::GetCurrentPosition() std::unique_ptr CreateOpenALPlayer(agi::AudioProvider *provider, wxWindow *) { - return agi::make_unique(provider); + return std::make_unique(provider); } #endif // WITH_OPENAL diff --git a/src/audio_player_oss.cpp b/src/audio_player_oss.cpp index 93950baef5..45a345a230 100644 --- a/src/audio_player_oss.cpp +++ b/src/audio_player_oss.cpp @@ -40,7 +40,6 @@ #include #include -#include #include #include @@ -198,7 +197,7 @@ void OSSPlayer::Play(int64_t start, int64_t count) start_frame = cur_frame = start; end_frame = start + count; - thread = agi::make_unique(this); + thread = std::make_unique(this); thread->Create(); thread->Run(); @@ -280,7 +279,7 @@ int64_t OSSPlayer::GetCurrentPosition() } std::unique_ptr CreateOSSPlayer(agi::AudioProvider *provider, wxWindow *) { - return agi::make_unique(provider); + return std::make_unique(provider); } #endif // WITH_OSS diff --git a/src/audio_player_portaudio.cpp b/src/audio_player_portaudio.cpp index 7a5babcdc1..fc161cf32f 100644 --- a/src/audio_player_portaudio.cpp +++ b/src/audio_player_portaudio.cpp @@ -42,7 +42,6 @@ #include #include -#include // Uncomment to enable extremely spammy debug logging //#define PORTAUDIO_DEBUG @@ -280,7 +279,7 @@ bool PortAudioPlayer::IsPlaying() { } std::unique_ptr CreatePortAudioPlayer(agi::AudioProvider *provider, wxWindow *) { - return agi::make_unique(provider); + return std::make_unique(provider); } #endif // WITH_PORTAUDIO diff --git a/src/audio_player_pulse.cpp b/src/audio_player_pulse.cpp index 7174356bdd..9ccf6536ab 100644 --- a/src/audio_player_pulse.cpp +++ b/src/audio_player_pulse.cpp @@ -40,7 +40,6 @@ #include #include -#include #include #include @@ -322,6 +321,6 @@ void PulseAudioPlayer::pa_stream_notify(pa_stream *p, PulseAudioPlayer *thread) } std::unique_ptr CreatePulseAudioPlayer(agi::AudioProvider *provider, wxWindow *) { - return agi::make_unique(provider); + return std::make_unique(provider); } #endif // WITH_LIBPULSE diff --git a/src/audio_provider_avs.cpp b/src/audio_provider_avs.cpp index b94cb4df4c..895e8c0283 100644 --- a/src/audio_provider_avs.cpp +++ b/src/audio_provider_avs.cpp @@ -45,7 +45,6 @@ #include #include #include -#include #include @@ -58,12 +57,12 @@ class AvisynthAudioProvider final : public agi::AudioProvider { void FillBuffer(void *buf, int64_t start, int64_t count) const; public: - AvisynthAudioProvider(agi::fs::path const& filename); + AvisynthAudioProvider(std::filesystem::path const& filename); bool NeedsCache() const override { return true; } }; -AvisynthAudioProvider::AvisynthAudioProvider(agi::fs::path const& filename) { +AvisynthAudioProvider::AvisynthAudioProvider(std::filesystem::path const& filename) { agi::acs::CheckFileRead(filename); std::lock_guard lock(avs_wrapper.GetMutex()); @@ -80,7 +79,7 @@ AvisynthAudioProvider::AvisynthAudioProvider(agi::fs::path const& filename) { AVSValue args[3] = { env->SaveString(agi::fs::ShortName(filename).c_str()), false, true }; // Load DirectShowSource.dll from app dir if it exists - agi::fs::path dsspath(config::path->Decode("?data/DirectShowSource.dll")); + std::filesystem::path dsspath(config::path->Decode("?data/DirectShowSource.dll")); if (agi::fs::FileExists(dsspath)) env->Invoke("LoadPlugin", env->SaveString(agi::fs::ShortName(dsspath).c_str())); @@ -143,7 +142,7 @@ void AvisynthAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) } } -std::unique_ptr CreateAvisynthAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *) { - return agi::make_unique(file); +std::unique_ptr CreateAvisynthAudioProvider(std::filesystem::path const& file, agi::BackgroundRunner *) { + return std::make_unique(file); } #endif diff --git a/src/audio_provider_factory.cpp b/src/audio_provider_factory.cpp index 887783644d..d849f019b9 100644 --- a/src/audio_provider_factory.cpp +++ b/src/audio_provider_factory.cpp @@ -24,8 +24,7 @@ #include #include #include - -#include +#include using namespace agi; @@ -39,7 +38,7 @@ struct factory { bool hidden; }; -const factory providers[] = { +const std::initializer_list providers = { {"Dummy", CreateDummyAudioProvider, true}, {"PCM", CreatePCMAudioProvider, true}, #ifdef WITH_FFMS2 @@ -52,14 +51,14 @@ const factory providers[] = { } std::vector GetAudioProviderNames() { - return ::GetClasses(boost::make_iterator_range(std::begin(providers), std::end(providers))); + return ::GetClasses(providers); } std::unique_ptr GetAudioProvider(fs::path const& filename, Path const& path_helper, BackgroundRunner *br) { auto preferred = OPT_GET("Audio/Provider")->GetString(); - auto sorted = GetSorted(boost::make_iterator_range(std::begin(providers), std::end(providers)), preferred); + auto sorted = GetSorted(providers, preferred); std::unique_ptr provider; bool found_file = false; @@ -76,18 +75,18 @@ std::unique_ptr GetAudioProvider(fs::path const& filename, } catch (fs::FileNotFound const& err) { LOG_D("audio_provider") << err.GetMessage(); - msg_all += std::string(factory->name) + ": " + err.GetMessage() + " not found.\n"; + agi::AppendStr(msg_all, factory->name, ": ", err.GetMessage(), " not found.\n"); } catch (AudioDataNotFound const& err) { LOG_D("audio_provider") << err.GetMessage(); found_file = true; - msg_all += std::string(factory->name) + ": " + err.GetMessage() + "\n"; + agi::AppendStr(msg_all, factory->name, ": ", err.GetMessage(), "\n"); } catch (AudioProviderError const& err) { LOG_D("audio_provider") << err.GetMessage(); found_audio = true; found_file = true; - std::string thismsg = std::string(factory->name) + ": " + err.GetMessage() + "\n"; + std::string thismsg = agi::Str(factory->name, ": ", err.GetMessage(), "\n"); msg_all += thismsg; msg_partial += thismsg; } @@ -108,7 +107,7 @@ std::unique_ptr GetAudioProvider(fs::path const& filename, provider = CreateConvertAudioProvider(std::move(provider)); // Change provider to RAM/HD cache if needed - int cache = OPT_GET("Audio/Cache/Type")->GetInt(); + auto cache = OPT_GET("Audio/Cache/Type")->GetInt(); if (!cache || !needs_cache) return CreateLockAudioProvider(std::move(provider)); diff --git a/src/audio_provider_factory.h b/src/audio_provider_factory.h index b419d6d0a2..a8a58646f5 100644 --- a/src/audio_provider_factory.h +++ b/src/audio_provider_factory.h @@ -14,8 +14,7 @@ // // Aegisub Project http://www.aegisub.org/ -#include - +#include #include #include @@ -25,7 +24,7 @@ namespace agi { class Path; } -std::unique_ptr GetAudioProvider(agi::fs::path const& filename, +std::unique_ptr GetAudioProvider(std::filesystem::path const& filename, agi::Path const& path_helper, agi::BackgroundRunner *br); std::vector GetAudioProviderNames(); diff --git a/src/audio_provider_ffmpegsource.cpp b/src/audio_provider_ffmpegsource.cpp index 0a201855ac..76cd7ab307 100644 --- a/src/audio_provider_ffmpegsource.cpp +++ b/src/audio_provider_ffmpegsource.cpp @@ -39,7 +39,6 @@ #include "options.h" #include -#include #include @@ -51,21 +50,21 @@ class FFmpegSourceAudioProvider final : public agi::AudioProvider, FFmpegSourceP mutable char FFMSErrMsg[1024]; ///< FFMS error message mutable FFMS_ErrorInfo ErrInfo; ///< FFMS error codes/messages - void LoadAudio(agi::fs::path const& filename); + void LoadAudio(std::filesystem::path const& filename); void FillBuffer(void *Buf, int64_t Start, int64_t Count) const override { if (FFMS_GetAudio(AudioSource, Buf, Start, Count, &ErrInfo)) throw agi::AudioDecodeError(std::string("Failed to get audio samples: ") + ErrInfo.Buffer); } public: - FFmpegSourceAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *br); + FFmpegSourceAudioProvider(std::filesystem::path const& filename, agi::BackgroundRunner *br); bool NeedsCache() const override { return true; } }; /// @brief Constructor /// @param filename The filename to open -FFmpegSourceAudioProvider::FFmpegSourceAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *br) try +FFmpegSourceAudioProvider::FFmpegSourceAudioProvider(std::filesystem::path const& filename, agi::BackgroundRunner *br) try : FFmpegSourceProvider(br) , AudioSource(nullptr, FFMS_DestroyAudioSource) { @@ -81,7 +80,7 @@ catch (agi::EnvironmentError const& err) { throw agi::AudioProviderError(err.GetMessage()); } -void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) { +void FFmpegSourceAudioProvider::LoadAudio(std::filesystem::path const& filename) { FFMS_Indexer *Indexer = FFMS_CreateIndexer(filename.string().c_str(), &ErrInfo); if (!Indexer) { if (ErrInfo.SubType == FFMS_ERROR_FILE_READ) @@ -107,7 +106,7 @@ void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) { throw agi::AudioDataNotFound("no audio tracks found"); // generate a name for the cache file - agi::fs::path CacheName = GetCacheFilename(filename); + std::filesystem::path CacheName = GetCacheFilename(filename); // try to read index agi::scoped_holder @@ -182,8 +181,8 @@ void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) { } -std::unique_ptr CreateFFmpegSourceAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *br) { - return agi::make_unique(file, br); +std::unique_ptr CreateFFmpegSourceAudioProvider(std::filesystem::path const& file, agi::BackgroundRunner *br) { + return std::make_unique(file, br); } #endif /* WITH_FFMS2 */ diff --git a/src/audio_renderer.cpp b/src/audio_renderer.cpp index e90586b026..4befd15be1 100644 --- a/src/audio_renderer.cpp +++ b/src/audio_renderer.cpp @@ -34,7 +34,6 @@ #include "audio_renderer.h" #include -#include #include #include @@ -57,7 +56,7 @@ AudioRendererBitmapCacheBitmapFactory::AudioRendererBitmapCacheBitmapFactory(Aud std::unique_ptr AudioRendererBitmapCacheBitmapFactory::ProduceBlock(int /* i */) { - return agi::make_unique(renderer->cache_bitmap_width, renderer->pixel_height, 24); + return std::make_unique(renderer->cache_bitmap_width, renderer->pixel_height, 24); } size_t AudioRendererBitmapCacheBitmapFactory::GetBlockSize() const diff --git a/src/audio_renderer_spectrum.cpp b/src/audio_renderer_spectrum.cpp index 4331febfc3..4e818afb00 100644 --- a/src/audio_renderer_spectrum.cpp +++ b/src/audio_renderer_spectrum.cpp @@ -40,7 +40,6 @@ #endif #include -#include #include @@ -115,7 +114,7 @@ void AudioSpectrumRenderer::RecreateCache() if (provider) { size_t block_count = (size_t)((provider->GetNumSamples() + ((size_t)1<> derivation_dist); - cache = agi::make_unique(block_count, this); + cache = std::make_unique(block_count, this); #ifdef WITH_FFTW3 dft_input = fftw_alloc_real(2< CreateDialogueTimingController(agi::Conte /// @brief Create a karaoke audio timing controller /// @param c Project context /// @param kara Karaoke model -std::unique_ptr CreateKaraokeTimingController(agi::Context *c, AssKaraoke *kara, agi::signal::Connection& file_changed); +std::unique_ptr CreateKaraokeTimingController(agi::Context *c, agi::ass::Karaoke *kara, agi::signal::Connection& file_changed); diff --git a/src/audio_timing_dialogue.cpp b/src/audio_timing_dialogue.cpp index 7762525f2a..2735898149 100644 --- a/src/audio_timing_dialogue.cpp +++ b/src/audio_timing_dialogue.cpp @@ -40,7 +40,6 @@ #include "utils.h" #include -#include #include #include @@ -924,5 +923,5 @@ int AudioTimingControllerDialogue::SnapMarkers(int snap_range, std::vector