From d3cfa20db91be58792a587d536fa105401dcdb22 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Thu, 4 Apr 2013 19:53:59 -0700 Subject: [PATCH] Fix unicode issues in the karaoke split/join bar Use ICU to split the text into characters rather than assuming that one wchar_t == one character in some places, and one char == one character in other places. --- aegisub/src/ass_karaoke.cpp | 2 +- aegisub/src/audio_karaoke.cpp | 121 ++++++++++++++++++++-------------- aegisub/src/audio_karaoke.h | 17 +++-- 3 files changed, 86 insertions(+), 54 deletions(-) diff --git a/aegisub/src/ass_karaoke.cpp b/aegisub/src/ass_karaoke.cpp index a199dce53..88c661f85 100644 --- a/aegisub/src/ass_karaoke.cpp +++ b/aegisub/src/ass_karaoke.cpp @@ -266,7 +266,7 @@ void AssKaraoke::SetLineTimes(int start_time, int end_time) { syls[idx].duration = std::max(0, syls[idx].duration - delta); } while (++idx < syls.size() && syls[idx].start_time < start_time); - // And truncate any syllabls ending after the new end_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; diff --git a/aegisub/src/audio_karaoke.cpp b/aegisub/src/audio_karaoke.cpp index 4fd1720c9..c1e1beea6 100644 --- a/aegisub/src/audio_karaoke.cpp +++ b/aegisub/src/audio_karaoke.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2011, Thomas Goyne +// 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 @@ -23,9 +23,6 @@ #include "audio_karaoke.h" -#include -#include - #include "include/aegisub/context.h" #include "ass_dialogue.h" @@ -40,6 +37,10 @@ #include "selection_controller.h" #include "utils.h" +#include +#include +#include + #include #include #include @@ -65,9 +66,13 @@ AudioKaraoke::AudioKaraoke(wxWindow *parent, agi::Context *c) , audio_opened(c->audioController->AddAudioOpenListener(&AudioKaraoke::OnAudioOpened, this)) , audio_closed(c->audioController->AddAudioCloseListener(&AudioKaraoke::OnAudioClosed, this)) , active_line_changed(c->selectionController->AddActiveLineListener(&AudioKaraoke::OnActiveLineChanged, this)) -, active_line(0) +, active_line(nullptr) , kara(new AssKaraoke) , scroll_x(0) +, scroll_dir(0) +, char_height(0) +, char_width(0) +, mouse_pos(0) , click_will_remove_split(false) , enabled(false) { @@ -209,9 +214,8 @@ void AudioKaraoke::RenderText() { if (line_width > bmp_size.GetWidth()) bmp_size.SetWidth(line_width); - if (!rendered_line.IsOk() || bmp_size != rendered_line.GetSize()) { + if (!rendered_line.IsOk() || bmp_size != rendered_line.GetSize()) rendered_line = wxBitmap(bmp_size); - } wxMemoryDC dc(rendered_line); @@ -225,15 +229,13 @@ void AudioKaraoke::RenderText() { // Draw each character in the line int y = (bmp_size.GetHeight() - char_height) / 2; - for (size_t i = 0; i < spaced_text.size(); ++i) { + for (size_t i = 0; i < spaced_text.size(); ++i) dc.DrawText(spaced_text[i], char_x[i], y); - } // Draw the lines between each syllable dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT)); - for (size_t i = 0; i < syl_lines.size(); ++i) { + for (size_t i = 0; i < syl_lines.size(); ++i) dc.DrawLine(syl_lines[i], 0, syl_lines[i], bmp_size.GetHeight()); - } } void AudioKaraoke::AddMenuItem(wxMenu &menu, std::string const& tag, wxString const& help, std::string const& selected) { @@ -276,15 +278,12 @@ void AudioKaraoke::OnMouse(wxMouseEvent &event) { // Check if the mouse is over a scroll arrow int client_width = split_area->GetClientSize().GetWidth(); - if (scroll_x > 0 && mouse_pos < 20) { + if (scroll_x > 0 && mouse_pos < 20) scroll_dir = -1; - } - else if (scroll_x + client_width < rendered_line.GetWidth() && mouse_pos > client_width - 20) { + else if (scroll_x + client_width < rendered_line.GetWidth() && mouse_pos > client_width - 20) scroll_dir = 1; - } - else { + else scroll_dir = 0; - } if (scroll_dir) { mouse_pos = -1; @@ -301,7 +300,7 @@ void AudioKaraoke::OnMouse(wxMouseEvent &event) { int shifted_pos = mouse_pos + scroll_x; // Character to insert the new split point before - int split_pos = std::min((shifted_pos - char_width / 2) / char_width, spaced_text.size()); + int split_pos = std::min((shifted_pos + char_width / 2) / char_width, spaced_text.size()); // Syllable this character is in int syl = last_lt_or_eq(syl_start_points, split_pos); @@ -318,12 +317,10 @@ void AudioKaraoke::OnMouse(wxMouseEvent &event) { return; } - if (click_will_remove_split) { + if (click_will_remove_split) kara->RemoveSplit(syl + (click_left && !click_right)); - } - else { - kara->AddSplit(syl, split_pos - syl_start_points[syl]); - } + else + kara->AddSplit(syl, char_to_byte[split_pos] - 1); SetDisplayText(); accept_button->Enable(true); @@ -353,42 +350,70 @@ void AudioKaraoke::LoadFromLine() { } void AudioKaraoke::SetDisplayText() { - // Insert spaces between each syllable to avoid crowding - spaced_text.clear(); - syl_start_points.clear(); - syl_start_points.reserve(kara->size()); - for (auto const& syl : *kara) { - syl_start_points.push_back(spaced_text.size()); - spaced_text += to_wx(" " + syl.text); - } + using namespace boost::locale::boundary; - // Get the x-coordinates of the right edge of each character wxMemoryDC dc; dc.SetFont(split_font); - wxArrayInt p_char_x; - dc.GetPartialTextExtents(spaced_text, p_char_x); - // Convert the partial sub to the the width of each character - std::adjacent_difference(p_char_x.begin(), p_char_x.end(), p_char_x.begin()); + auto get_char_width = [&](std::string const& character) -> int { + const auto it = char_widths.find(character); + if (it != end(char_widths)) + return it->second; - // Get the maximum character width - char_width = *std::max_element(p_char_x.begin(), p_char_x.end()); + const auto size = dc.GetTextExtent(to_wx(character)); + char_height = std::max(char_height, size.GetHeight()); + char_widths[character] = size.GetWidth(); + return size.GetWidth(); + }; + + char_width = get_char_width(" "); + + // Width in pixels of each character in this string + std::vector str_char_widths; + + spaced_text.clear(); + char_to_byte.clear(); + syl_start_points.clear(); + for (auto const& syl : *kara) { + // The last (and only the last) syllable needs the width of the final + // character in the syllable, so we unconditionally add it at the end + // of this loop, then remove the extra ones here + if (!char_to_byte.empty()) + char_to_byte.pop_back(); + + syl_start_points.push_back(spaced_text.size()); + + // Add a space between each syllable to avoid crowding + spaced_text.emplace_back(wxS(" ")); + str_char_widths.push_back(char_width); + 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) { + // Calculate the width in pixels of this character + const std::string character = chr.str(); + const int width = get_char_width(character); + char_width = std::max(char_width, width); + str_char_widths.push_back(width); + + spaced_text.emplace_back(to_wx(character)); + char_to_byte.push_back(syl_idx); + syl_idx += character.size(); + } + + char_to_byte.push_back(syl_idx); + } // Center each character within the space available to it - char_x.resize(p_char_x.size()); - for (size_t i = 0; i < p_char_x.size(); ++i) { - char_x[i] = i * char_width + (char_width - p_char_x[i]) / 2; - } + char_x.resize(str_char_widths.size()); + for (size_t i = 0; i < str_char_widths.size(); ++i) + char_x[i] = i * char_width + (char_width - str_char_widths[i]) / 2; // Calculate the positions of the syllable divider lines syl_lines.resize(syl_start_points.size() - 1); - for (size_t i = 1; i < syl_start_points.size(); ++i) { + for (size_t i = 1; i < syl_start_points.size(); ++i) syl_lines[i - 1] = syl_start_points[i] * char_width + char_width / 2; - } - - // Get line height - wxSize extents = dc.GetTextExtent(spaced_text); - char_height = extents.GetHeight(); RenderText(); } diff --git a/aegisub/src/audio_karaoke.h b/aegisub/src/audio_karaoke.h index eb42556bc..46b362777 100644 --- a/aegisub/src/audio_karaoke.h +++ b/aegisub/src/audio_karaoke.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011, Thomas Goyne +// 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 @@ -19,16 +19,17 @@ /// @ingroup audio_ui /// +#include +#include + #include +#include #include #include #include #include -#include -#include - class AssDialogue; class AssEntry; class AssKaraoke; @@ -80,7 +81,7 @@ class AudioKaraoke : public wxWindow { agi::scoped_ptr kara; /// Current line's stripped text with spaces added between each syllable - wxString spaced_text; + std::vector spaced_text; /// spaced_text + syl_lines rendered to a bitmap wxBitmap rendered_line; @@ -94,6 +95,12 @@ class AudioKaraoke : public wxWindow { /// Left x coordinate of each character in spaced_text in pixels std::vector char_x; + /// Mapping from character index to byte position in the relevant syllable's text + std::vector char_to_byte; + + /// Cached width of characters from GetTextExtent + std::unordered_map char_widths; + int scroll_x; ///< Distance the display has been shifted to the left in pixels int scroll_dir; ///< Direction the display will be scrolled on scroll_timer ticks (+/- 1) wxTimer scroll_timer; ///< Timer to scroll every 50ms when user holds down scroll button