// Copyright (c) 2006, Niels Martin Hansen // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of the Aegisub Group nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // Aegisub Project http://www.aegisub.org/ #include "auto4_base.h" #include "ass_file.h" #include "ass_style.h" #include "compat.h" #include "dialog_progress.h" #include "include/aegisub/context.h" #include "options.h" #include "string_codec.h" #include "subs_controller.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __WINDOWS__ #define WIN32_LEAN_AND_MEAN #include #include #endif namespace Automation4 { bool CalculateTextExtents(AssStyle *style, std::string const& text, double &width, double &height, double &descent, double &extlead) { width = height = descent = extlead = 0; double fontsize = style->fontsize * 64; double spacing = style->spacing * 64; #ifdef WIN32 // This is almost copypasta from TextSub auto dc = CreateCompatibleDC(nullptr); if (!dc) return false; SetMapMode(dc, MM_TEXT); LOGFONTW lf = {0}; lf.lfHeight = (LONG)fontsize; lf.lfWeight = style->bold ? FW_BOLD : FW_NORMAL; lf.lfItalic = style->italic; lf.lfUnderline = style->underline; lf.lfStrikeOut = style->strikeout; lf.lfCharSet = style->encoding; lf.lfOutPrecision = OUT_TT_PRECIS; lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; lf.lfQuality = ANTIALIASED_QUALITY; lf.lfPitchAndFamily = DEFAULT_PITCH|FF_DONTCARE; wcsncpy(lf.lfFaceName, agi::charset::ConvertW(style->font).c_str(), 31); auto font = CreateFontIndirect(&lf); if (!font) return false; auto old_font = SelectObject(dc, font); std::wstring wtext(agi::charset::ConvertW(text)); if (spacing != 0 ) { width = 0; for (auto c : wtext) { SIZE sz; GetTextExtentPoint32(dc, &c, 1, &sz); width += sz.cx + spacing; height = sz.cy; } } else { SIZE sz; GetTextExtentPoint32(dc, &wtext[0], (int)wtext.size(), &sz); width = sz.cx; height = sz.cy; } TEXTMETRIC tm; GetTextMetrics(dc, &tm); descent = tm.tmDescent; extlead = tm.tmExternalLeading; SelectObject(dc, old_font); DeleteObject(font); DeleteObject(dc); #else // not WIN32 wxMemoryDC thedc; // fix fontsize to be 72 DPI //fontsize = -FT_MulDiv((int)(fontsize+0.5), 72, thedc.GetPPI().y); // now try to get a font! // use the font list to get some caching... (chance is the script will need the same font very often) // USING wxTheFontList SEEMS TO CAUSE BAD LEAKS! //wxFont *thefont = wxTheFontList->FindOrCreateFont( wxFont thefont( (int)fontsize, wxFONTFAMILY_DEFAULT, style->italic ? wxFONTSTYLE_ITALIC : wxFONTSTYLE_NORMAL, style->bold ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL, style->underline, to_wx(style->font), wxFONTENCODING_SYSTEM); // FIXME! make sure to get the right encoding here, make some translation table between windows and wx encodings thedc.SetFont(thefont); wxString wtext(to_wx(text)); if (spacing) { // If there's inter-character spacing, kerning info must not be used, so calculate width per character // NOTE: Is kerning actually done either way?! for (auto const& wc : wtext) { int a, b, c, d; thedc.GetTextExtent(wc, &a, &b, &c, &d); double scaling = fontsize / (double)(b > 0 ? b : 1); // semi-workaround for missing OS/2 table data for scaling width += (a + spacing)*scaling; height = b > height ? b*scaling : height; descent = c > descent ? c*scaling : descent; extlead = d > extlead ? d*scaling : extlead; } } else { // If the inter-character spacing should be zero, kerning info can (and must) be used, so calculate everything in one go wxCoord lwidth, lheight, ldescent, lextlead; thedc.GetTextExtent(wtext, &lwidth, &lheight, &ldescent, &lextlead); double scaling = fontsize / (double)(lheight > 0 ? lheight : 1); // semi-workaround for missing OS/2 table data for scaling width = lwidth*scaling; height = lheight*scaling; descent = ldescent*scaling; extlead = lextlead*scaling; } #endif // Compensate for scaling width = style->scalex / 100 * width / 64; height = style->scaley / 100 * height / 64; descent = style->scaley / 100 * descent / 64; extlead = style->scaley / 100 * extlead / 64; return true; } ExportFilter::ExportFilter(std::string const& name, std::string const& description, int priority) : AssExportFilter(name, description, priority) { } std::string ExportFilter::GetScriptSettingsIdentifier() { return inline_string_encode(GetName()); } wxWindow* ExportFilter::GetConfigDialogWindow(wxWindow *parent, agi::Context *c) { config_dialog = GenerateConfigDialog(parent, c); if (config_dialog) { std::string const& val = c->ass->Properties.automation_settings[GetScriptSettingsIdentifier()]; if (!val.empty()) config_dialog->Unserialise(val); return config_dialog->CreateWindow(parent); } return nullptr; } void ExportFilter::LoadSettings(bool is_default, agi::Context *c) { if (config_dialog) c->ass->Properties.automation_settings[GetScriptSettingsIdentifier()] = config_dialog->Serialise(); } // ProgressSink ProgressSink::ProgressSink(agi::ProgressSink *impl, BackgroundScriptRunner *bsr) : impl(impl) , bsr(bsr) , trace_level(OPT_GET("Automation/Trace Level")->GetInt()) { } void ProgressSink::ShowDialog(ScriptDialog *config_dialog) { agi::dispatch::Main().Sync([=] { wxDialog w; // container dialog box w.SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY); w.Create(bsr->GetParentWindow(), -1, to_wx(bsr->GetTitle())); auto s = new wxBoxSizer(wxHORIZONTAL); // sizer for putting contents in wxWindow *ww = config_dialog->CreateWindow(&w); // generate actual dialog contents s->Add(ww, 0, wxALL, 5); // add contents to dialog w.SetSizerAndFit(s); w.CenterOnParent(); w.ShowModal(); }); } int ProgressSink::ShowDialog(wxDialog *dialog) { int ret = 0; agi::dispatch::Main().Sync([&] { ret = dialog->ShowModal(); }); return ret; } BackgroundScriptRunner::BackgroundScriptRunner(wxWindow *parent, std::string const& title) : impl(new DialogProgress(parent, to_wx(title))) { } BackgroundScriptRunner::~BackgroundScriptRunner() { } void BackgroundScriptRunner::Run(std::function task) { impl->Run([&](agi::ProgressSink *ps) { ProgressSink aps(ps, this); task(&aps); }); } wxWindow *BackgroundScriptRunner::GetParentWindow() const { return impl.get(); } std::string BackgroundScriptRunner::GetTitle() const { return from_wx(impl->GetTitle()); } // Script Script::Script(agi::fs::path const& filename) : filename(filename) { include_path.emplace_back(filename.parent_path()); std::string include_paths = OPT_GET("Path/Automation/Include")->GetString(); for (auto tok : agi::Split(include_paths, '|')) { auto path = config::path->Decode(agi::str(tok)); if (path.is_absolute() && agi::fs::DirectoryExists(path)) include_path.emplace_back(std::move(path)); } } // ScriptManager void ScriptManager::Add(std::unique_ptr