diff --git a/build/libaegisub/libaegisub.vcxproj b/build/libaegisub/libaegisub.vcxproj
index dd935e6d9..030a8eda8 100644
--- a/build/libaegisub/libaegisub.vcxproj
+++ b/build/libaegisub/libaegisub.vcxproj
@@ -46,6 +46,7 @@
+
@@ -91,6 +92,7 @@
+
diff --git a/build/libaegisub/libaegisub.vcxproj.filters b/build/libaegisub/libaegisub.vcxproj.filters
index 978e44fe8..9af2f8403 100644
--- a/build/libaegisub/libaegisub.vcxproj.filters
+++ b/build/libaegisub/libaegisub.vcxproj.filters
@@ -167,6 +167,9 @@
Header Files
+
+ Header Files
+
@@ -274,6 +277,9 @@
Source Files\Common
+
+ Source Files\Common
+
diff --git a/libaegisub/Makefile b/libaegisub/Makefile
index f56f38dc5..4ad09605c 100644
--- a/libaegisub/Makefile
+++ b/libaegisub/Makefile
@@ -19,6 +19,7 @@ SRC += \
common/cajun/reader.cpp \
common/cajun/writer.cpp \
common/calltip_provider.cpp \
+ common/character_count.cpp \
common/charset.cpp \
common/charset_6937.cpp \
common/charset_conv.cpp \
diff --git a/libaegisub/common/character_count.cpp b/libaegisub/common/character_count.cpp
new file mode 100644
index 000000000..20ea87510
--- /dev/null
+++ b/libaegisub/common/character_count.cpp
@@ -0,0 +1,92 @@
+// Copyright (c) 2014, 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 "libaegisub/character_count.h"
+
+#include "libaegisub/ass/dialogue_parser.h"
+
+#include
+#include
+#include
+#include
+
+namespace {
+template
+size_t count_in_range(Iterator begin, Iterator end, bool ignore_whitespace) {
+ using namespace boost::locale::boundary;
+ const ssegment_index characters(character, begin, end);
+ if (!ignore_whitespace)
+ return boost::distance(characters);
+
+ // characters.rule(word_any) doesn't seem to work for character indexes (everything is word_none)
+ size_t count = 0;
+ for (auto const& chr : characters) {
+ UChar32 c;
+ int i = 0;
+ U8_NEXT_UNSAFE(chr.begin(), i, c);
+ if (!u_isUWhiteSpace(c))
+ ++count;
+ }
+ return count;
+}
+}
+
+namespace agi {
+size_t CharacterCount(std::string const& str) {
+ size_t characters = 0;
+ auto pos = begin(str);
+ do {
+ auto it = std::find(pos, end(str), '{');
+ characters += count_in_range(pos, it, true);
+ if (it == end(str)) break;
+
+ pos = std::find(pos, end(str), '}');
+ if (pos == end(str)) {
+ characters += count_in_range(it, pos, true);
+ break;
+ }
+ } while (++pos != end(str));
+
+ return characters;
+}
+
+size_t MaxLineLength(std::string const& text, bool ignore_whitespace) {
+ auto tokens = agi::ass::TokenizeDialogueBody(text);
+ agi::ass::MarkDrawings(text, tokens);
+
+ size_t pos = 0;
+ size_t max_line_length = 0;
+ size_t current_line_length = 0;
+ for (auto token : tokens) {
+ if (token.type == agi::ass::DialogueTokenType::LINE_BREAK) {
+ if (text[pos + 1] == 'h') {
+ if (!ignore_whitespace)
+ current_line_length += 1;
+ }
+ else { // N or n
+ max_line_length = std::max(max_line_length, current_line_length);
+ current_line_length = 0;
+ }
+ }
+ else if (token.type == agi::ass::DialogueTokenType::TEXT)
+ current_line_length += count_in_range(begin(text) + pos, begin(text) + pos + token.length, ignore_whitespace);
+
+ pos += token.length;
+ }
+
+ return std::max(max_line_length, current_line_length);
+}
+}
diff --git a/libaegisub/include/libaegisub/character_count.h b/libaegisub/include/libaegisub/character_count.h
new file mode 100644
index 000000000..512197a93
--- /dev/null
+++ b/libaegisub/include/libaegisub/character_count.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2014, 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
+
+namespace agi {
+ /// Get the length in characters of the longest line in the given text
+ size_t MaxLineLength(std::string const& text, bool ignore_whitespace);
+ size_t CharacterCount(std::string const& str);
+}
\ No newline at end of file
diff --git a/src/grid_column.cpp b/src/grid_column.cpp
index 468c2d6cd..c4de1d22e 100644
--- a/src/grid_column.cpp
+++ b/src/grid_column.cpp
@@ -21,9 +21,10 @@
#include "compat.h"
#include "include/aegisub/context.h"
#include "options.h"
-#include "utils.h"
#include "video_context.h"
+#include
+
#include
int WidthHelper::operator()(boost::flyweight const& str) {
@@ -223,28 +224,13 @@ struct GridColumnCPS final : GridColumn {
bool RefreshOnTextChange() const override { return true; }
wxString Value(const AssDialogue *d, const agi::Context *) const override {
- int characters = 0;
-
int duration = d->End - d->Start;
auto const& text = d->Text.get();
if (duration <= 0 || text.size() > static_cast(duration))
return wxS("");
- auto pos = begin(text);
- do {
- auto it = std::find(pos, end(text), '{');
- characters += CharacterCount(pos, it, true);
- if (it == end(text)) break;
-
- pos = std::find(pos, end(text), '}');
- if (pos == end(text)) {
- characters += CharacterCount(it, pos, true);
- break;
- }
- } while (++pos != end(text));
-
- return std::to_wstring(characters * 1000 / duration);
+ return std::to_wstring(agi::CharacterCount(text) * 1000 / duration);
}
int Width(const agi::Context *c, WidthHelper &helper, bool) const override {
diff --git a/src/placeholder_ctrl.h b/src/placeholder_ctrl.h
index 34caa115c..9e4bd3721 100644
--- a/src/placeholder_ctrl.h
+++ b/src/placeholder_ctrl.h
@@ -14,12 +14,11 @@
//
// Aegisub Project http://www.aegisub.org/
-/// @file placeholder_ctrl.h
-/// @ingroup custom_control
-///
-
#include
+// Defined in osx_utils.mm
+void SetPlaceholderText(wxWindow *window, wxString const& placeholder);
+
/// @class Placeholder
/// @brief A wrapper around a control to add placeholder text
///
diff --git a/src/subs_edit_box.cpp b/src/subs_edit_box.cpp
index 69e82f566..3c264bb6a 100644
--- a/src/subs_edit_box.cpp
+++ b/src/subs_edit_box.cpp
@@ -50,10 +50,10 @@
#include "text_selection_controller.h"
#include "timeedit_ctrl.h"
#include "tooltip_manager.h"
-#include "utils.h"
#include "validators.h"
#include "video_context.h"
+#include
#include
#include
@@ -228,7 +228,7 @@ wxTextCtrl *SubsEditBox::MakeMarginCtrl(wxString const& tooltip, int margin, wxS
middle_left_sizer->Add(ctrl, wxSizerFlags().Center());
Bind(wxEVT_TEXT, [=](wxCommandEvent&) {
- int value = mid(0, atoi(ctrl->GetValue().utf8_str()), 9999);
+ int value = agi::util::mid(0, atoi(ctrl->GetValue().utf8_str()), 9999);
SetSelectedRows([&](AssDialogue *d) { d->Margin[margin] = value; },
commit_msg, AssFile::COMMIT_DIAG_META);
}, ctrl->GetId());
@@ -587,7 +587,7 @@ void SubsEditBox::CallCommand(const char *cmd_name) {
void SubsEditBox::UpdateCharacterCount(std::string const& text) {
auto ignore_whitespace = OPT_GET("Subtitle/Character Counter/Ignore Whitespace")->GetBool();
agi::dispatch::Background().Async([=]{
- size_t length = MaxLineLength(text, ignore_whitespace);
+ size_t length = agi::MaxLineLength(text, ignore_whitespace);
agi::dispatch::Main().Async([=]{
char_count->SetValue(wxString::Format("%lu", (unsigned long)length));
size_t limit = (size_t)OPT_GET("Subtitle/Character Limit")->GetInt();
diff --git a/src/utils.cpp b/src/utils.cpp
index 7767b04bc..9ebd126f0 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -39,7 +39,6 @@
#include "options.h"
#include "retina_helper.h"
-#include
#include
#include
#include
@@ -49,11 +48,7 @@
#endif
#include
#include
-#include
-#include
#include