forked from mia/Aegisub
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.
This commit is contained in:
parent
e33dc4ae3a
commit
d3cfa20db9
3 changed files with 86 additions and 54 deletions
|
@ -266,7 +266,7 @@ void AssKaraoke::SetLineTimes(int start_time, int end_time) {
|
||||||
syls[idx].duration = std::max(0, syls[idx].duration - delta);
|
syls[idx].duration = std::max(0, syls[idx].duration - delta);
|
||||||
} while (++idx < syls.size() && syls[idx].start_time < start_time);
|
} 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;
|
idx = syls.size() - 1;
|
||||||
while (syls[idx].start_time > end_time) {
|
while (syls[idx].start_time > end_time) {
|
||||||
syls[idx].start_time = end_time;
|
syls[idx].start_time = end_time;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
|
// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
|
||||||
//
|
//
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
@ -23,9 +23,6 @@
|
||||||
|
|
||||||
#include "audio_karaoke.h"
|
#include "audio_karaoke.h"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <numeric>
|
|
||||||
|
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
|
|
||||||
#include "ass_dialogue.h"
|
#include "ass_dialogue.h"
|
||||||
|
@ -40,6 +37,10 @@
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <boost/locale/boundary.hpp>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
#include <wx/bmpbuttn.h>
|
#include <wx/bmpbuttn.h>
|
||||||
#include <wx/button.h>
|
#include <wx/button.h>
|
||||||
#include <wx/dcclient.h>
|
#include <wx/dcclient.h>
|
||||||
|
@ -65,9 +66,13 @@ AudioKaraoke::AudioKaraoke(wxWindow *parent, agi::Context *c)
|
||||||
, audio_opened(c->audioController->AddAudioOpenListener(&AudioKaraoke::OnAudioOpened, this))
|
, audio_opened(c->audioController->AddAudioOpenListener(&AudioKaraoke::OnAudioOpened, this))
|
||||||
, audio_closed(c->audioController->AddAudioCloseListener(&AudioKaraoke::OnAudioClosed, this))
|
, audio_closed(c->audioController->AddAudioCloseListener(&AudioKaraoke::OnAudioClosed, this))
|
||||||
, active_line_changed(c->selectionController->AddActiveLineListener(&AudioKaraoke::OnActiveLineChanged, this))
|
, active_line_changed(c->selectionController->AddActiveLineListener(&AudioKaraoke::OnActiveLineChanged, this))
|
||||||
, active_line(0)
|
, active_line(nullptr)
|
||||||
, kara(new AssKaraoke)
|
, kara(new AssKaraoke)
|
||||||
, scroll_x(0)
|
, scroll_x(0)
|
||||||
|
, scroll_dir(0)
|
||||||
|
, char_height(0)
|
||||||
|
, char_width(0)
|
||||||
|
, mouse_pos(0)
|
||||||
, click_will_remove_split(false)
|
, click_will_remove_split(false)
|
||||||
, enabled(false)
|
, enabled(false)
|
||||||
{
|
{
|
||||||
|
@ -209,9 +214,8 @@ void AudioKaraoke::RenderText() {
|
||||||
if (line_width > bmp_size.GetWidth())
|
if (line_width > bmp_size.GetWidth())
|
||||||
bmp_size.SetWidth(line_width);
|
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);
|
rendered_line = wxBitmap(bmp_size);
|
||||||
}
|
|
||||||
|
|
||||||
wxMemoryDC dc(rendered_line);
|
wxMemoryDC dc(rendered_line);
|
||||||
|
|
||||||
|
@ -225,15 +229,13 @@ void AudioKaraoke::RenderText() {
|
||||||
|
|
||||||
// Draw each character in the line
|
// Draw each character in the line
|
||||||
int y = (bmp_size.GetHeight() - char_height) / 2;
|
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);
|
dc.DrawText(spaced_text[i], char_x[i], y);
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the lines between each syllable
|
// Draw the lines between each syllable
|
||||||
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
|
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());
|
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) {
|
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
|
// Check if the mouse is over a scroll arrow
|
||||||
int client_width = split_area->GetClientSize().GetWidth();
|
int client_width = split_area->GetClientSize().GetWidth();
|
||||||
if (scroll_x > 0 && mouse_pos < 20) {
|
if (scroll_x > 0 && mouse_pos < 20)
|
||||||
scroll_dir = -1;
|
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;
|
scroll_dir = 1;
|
||||||
}
|
else
|
||||||
else {
|
|
||||||
scroll_dir = 0;
|
scroll_dir = 0;
|
||||||
}
|
|
||||||
|
|
||||||
if (scroll_dir) {
|
if (scroll_dir) {
|
||||||
mouse_pos = -1;
|
mouse_pos = -1;
|
||||||
|
@ -301,7 +300,7 @@ void AudioKaraoke::OnMouse(wxMouseEvent &event) {
|
||||||
int shifted_pos = mouse_pos + scroll_x;
|
int shifted_pos = mouse_pos + scroll_x;
|
||||||
|
|
||||||
// Character to insert the new split point before
|
// Character to insert the new split point before
|
||||||
int split_pos = std::min<int>((shifted_pos - char_width / 2) / char_width, spaced_text.size());
|
int split_pos = std::min<int>((shifted_pos + char_width / 2) / char_width, spaced_text.size());
|
||||||
|
|
||||||
// Syllable this character is in
|
// Syllable this character is in
|
||||||
int syl = last_lt_or_eq(syl_start_points, split_pos);
|
int syl = last_lt_or_eq(syl_start_points, split_pos);
|
||||||
|
@ -318,12 +317,10 @@ void AudioKaraoke::OnMouse(wxMouseEvent &event) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (click_will_remove_split) {
|
if (click_will_remove_split)
|
||||||
kara->RemoveSplit(syl + (click_left && !click_right));
|
kara->RemoveSplit(syl + (click_left && !click_right));
|
||||||
}
|
else
|
||||||
else {
|
kara->AddSplit(syl, char_to_byte[split_pos] - 1);
|
||||||
kara->AddSplit(syl, split_pos - syl_start_points[syl]);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetDisplayText();
|
SetDisplayText();
|
||||||
accept_button->Enable(true);
|
accept_button->Enable(true);
|
||||||
|
@ -353,42 +350,70 @@ void AudioKaraoke::LoadFromLine() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioKaraoke::SetDisplayText() {
|
void AudioKaraoke::SetDisplayText() {
|
||||||
// Insert spaces between each syllable to avoid crowding
|
using namespace boost::locale::boundary;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the x-coordinates of the right edge of each character
|
|
||||||
wxMemoryDC dc;
|
wxMemoryDC dc;
|
||||||
dc.SetFont(split_font);
|
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
|
auto get_char_width = [&](std::string const& character) -> int {
|
||||||
std::adjacent_difference(p_char_x.begin(), p_char_x.end(), p_char_x.begin());
|
const auto it = char_widths.find(character);
|
||||||
|
if (it != end(char_widths))
|
||||||
|
return it->second;
|
||||||
|
|
||||||
// Get the maximum character width
|
const auto size = dc.GetTextExtent(to_wx(character));
|
||||||
char_width = *std::max_element(p_char_x.begin(), p_char_x.end());
|
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<int> 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
|
// Center each character within the space available to it
|
||||||
char_x.resize(p_char_x.size());
|
char_x.resize(str_char_widths.size());
|
||||||
for (size_t i = 0; i < p_char_x.size(); ++i) {
|
for (size_t i = 0; i < str_char_widths.size(); ++i)
|
||||||
char_x[i] = i * char_width + (char_width - p_char_x[i]) / 2;
|
char_x[i] = i * char_width + (char_width - str_char_widths[i]) / 2;
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the positions of the syllable divider lines
|
// Calculate the positions of the syllable divider lines
|
||||||
syl_lines.resize(syl_start_points.size() - 1);
|
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;
|
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();
|
RenderText();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
|
// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
|
||||||
//
|
//
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
@ -19,16 +19,17 @@
|
||||||
/// @ingroup audio_ui
|
/// @ingroup audio_ui
|
||||||
///
|
///
|
||||||
|
|
||||||
|
#include <libaegisub/scoped_ptr.h>
|
||||||
|
#include <libaegisub/signal.h>
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <wx/bitmap.h>
|
#include <wx/bitmap.h>
|
||||||
#include <wx/timer.h>
|
#include <wx/timer.h>
|
||||||
#include <wx/window.h>
|
#include <wx/window.h>
|
||||||
|
|
||||||
#include <libaegisub/scoped_ptr.h>
|
|
||||||
#include <libaegisub/signal.h>
|
|
||||||
|
|
||||||
class AssDialogue;
|
class AssDialogue;
|
||||||
class AssEntry;
|
class AssEntry;
|
||||||
class AssKaraoke;
|
class AssKaraoke;
|
||||||
|
@ -80,7 +81,7 @@ class AudioKaraoke : public wxWindow {
|
||||||
agi::scoped_ptr<AssKaraoke> kara;
|
agi::scoped_ptr<AssKaraoke> kara;
|
||||||
|
|
||||||
/// Current line's stripped text with spaces added between each syllable
|
/// Current line's stripped text with spaces added between each syllable
|
||||||
wxString spaced_text;
|
std::vector<wxString> spaced_text;
|
||||||
|
|
||||||
/// spaced_text + syl_lines rendered to a bitmap
|
/// spaced_text + syl_lines rendered to a bitmap
|
||||||
wxBitmap rendered_line;
|
wxBitmap rendered_line;
|
||||||
|
@ -94,6 +95,12 @@ class AudioKaraoke : public wxWindow {
|
||||||
/// Left x coordinate of each character in spaced_text in pixels
|
/// Left x coordinate of each character in spaced_text in pixels
|
||||||
std::vector<int> char_x;
|
std::vector<int> char_x;
|
||||||
|
|
||||||
|
/// Mapping from character index to byte position in the relevant syllable's text
|
||||||
|
std::vector<size_t> char_to_byte;
|
||||||
|
|
||||||
|
/// Cached width of characters from GetTextExtent
|
||||||
|
std::unordered_map<std::string, int> char_widths;
|
||||||
|
|
||||||
int scroll_x; ///< Distance the display has been shifted to the left in pixels
|
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)
|
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
|
wxTimer scroll_timer; ///< Timer to scroll every 50ms when user holds down scroll button
|
||||||
|
|
Loading…
Reference in a new issue