This commit is contained in:
Christian Kaiser 2025-07-25 21:16:16 +09:00 committed by GitHub
commit ca1e99bf70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 450 additions and 13 deletions

View File

@ -16,20 +16,23 @@
#include "app/commands/params.h" #include "app/commands/params.h"
#include "app/console.h" #include "app/console.h"
#include "app/doc.h" #include "app/doc.h"
#include "app/extensions.h"
#include "app/file/file.h" #include "app/file/file.h"
#include "app/file_selector.h" #include "app/file_selector.h"
#include "app/i18n/strings.h" #include "app/i18n/strings.h"
#include "app/modules/gui.h"
#include "app/pref/preferences.h" #include "app/pref/preferences.h"
#include "app/recent_files.h" #include "app/recent_files.h"
#include "app/ui/status_bar.h" #include "app/ui/status_bar.h"
#include "app/ui_context.h" #include "app/ui_context.h"
#include "app/util/open_file_job.h" #include "app/util/open_file_job.h"
#include "base/fs.h" #include "base/fs.h"
#include "base/thread.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "ui/ui.h" #include "ui/ui.h"
#ifdef ENABLE_SCRIPTING
#include "app/script/security.h"
#endif
#include <cstdio> #include <cstdio>
namespace app { namespace app {
@ -142,6 +145,13 @@ void OpenFileCommand::onExecute(Context* context)
return; return;
if (fop->hasError()) { if (fop->hasError()) {
#ifdef ENABLE_SCRIPTING
if (fop->hasUnknownFormatError() && loadCustomFormat(filename)) {
// If a script was detected and loaded the file without errors, then we can return safely
return;
}
#endif
console.printf(fop->error().c_str()); console.printf(fop->error().c_str());
unrecent = true; unrecent = true;
} }
@ -230,6 +240,32 @@ std::string OpenFileCommand::onGetFriendlyName() const
m_filename)))); m_filename))));
} }
#ifdef ENABLE_SCRIPTING
bool OpenFileCommand::loadCustomFormat(const std::string& filename)
{
const std::string detectedExtension = base::string_to_lower(base::get_file_extension(filename));
if (detectedExtension.empty())
return false;
for (const auto& customFormatExtension : App::instance()->extensions().customFormatList()) {
if (base::string_to_lower(customFormatExtension) == detectedExtension) {
for (Extension* extension : App::instance()->extensions()) {
if (!extension->isEnabled() || !extension->hasFileFormats())
continue;
auto formatId = extension->getCustomFormatIdForExtension(customFormatExtension,
Extension::FileFormat::Load);
if (formatId.has_value()) {
return extension->loadCustomFormat(*formatId, filename);
}
}
}
}
return false;
}
#endif
Command* CommandFactory::createOpenFileCommand() Command* CommandFactory::createOpenFileCommand()
{ {
return new OpenFileCommand; return new OpenFileCommand;

View File

@ -32,6 +32,10 @@ protected:
std::string onGetFriendlyName() const override; std::string onGetFriendlyName() const override;
private: private:
#ifdef ENABLE_SCRIPTING
bool loadCustomFormat(const std::string& filename);
#endif
std::string m_filename; std::string m_filename;
std::string m_folder; std::string m_folder;
bool m_ui; bool m_ui;

View File

@ -19,6 +19,7 @@
#include "app/context_access.h" #include "app/context_access.h"
#include "app/doc.h" #include "app/doc.h"
#include "app/doc_undo.h" #include "app/doc_undo.h"
#include "app/extensions.h"
#include "app/file/file.h" #include "app/file/file.h"
#include "app/file/gif_format.h" #include "app/file/gif_format.h"
#include "app/file/png_format.h" #include "app/file/png_format.h"
@ -207,12 +208,12 @@ void SaveFileBaseCommand::saveDocumentInBackground(const Context* context,
bounds = document->sprite()->bounds(); bounds = document->sprite()->bounds();
} }
FileOpROI roi(document, const FileOpROI roi(document,
bounds, bounds,
params().slice(), params().slice(),
params().tag(), params().tag(),
m_framesSeq, m_framesSeq,
m_adjustFramesByTag); m_adjustFramesByTag);
std::unique_ptr<FileOp> fop(FileOp::createSaveDocumentOperation(context, std::unique_ptr<FileOp> fop(FileOp::createSaveDocumentOperation(context,
roi, roi,
@ -222,13 +223,23 @@ void SaveFileBaseCommand::saveDocumentInBackground(const Context* context,
if (!fop) if (!fop)
return; return;
bool runJob = true;
#ifdef ENABLE_SCRIPTING
if (fop->hasUnknownFormatError() &&
saveCustomFormat(context, roi, filename, params().filenameFormat(), params().ignoreEmpty())) {
runJob = false;
}
#endif
if (resizeOnTheFly == ResizeOnTheFly::On) if (resizeOnTheFly == ResizeOnTheFly::On)
fop->setOnTheFlyScale(scale); fop->setOnTheFlyScale(scale);
SaveFileJob job(fop.get(), params().ui()); if (runJob) {
job.showProgressWindow(); SaveFileJob job(fop.get(), params().ui());
job.showProgressWindow();
}
if (fop->hasError()) { if (fop->hasError() && runJob) {
Console console; Console console;
console.printf(fop->error().c_str()); console.printf(fop->error().c_str());
@ -259,6 +270,49 @@ void SaveFileBaseCommand::saveDocumentInBackground(const Context* context,
} }
} }
#ifdef ENABLE_SCRIPTING
bool SaveFileBaseCommand::saveCustomFormat(const Context* context,
const FileOpROI& roi,
const std::string& filename,
const std::string& filenameFormatArg,
bool ignoreEmptyFrames)
{
const std::string detectedExtension = base::string_to_lower(base::get_file_extension(filename));
if (detectedExtension.empty())
return false;
for (const auto& customFormatExtension : App::instance()->extensions().customFormatList()) {
if (base::string_to_lower(customFormatExtension) == detectedExtension) {
for (Extension* extension : App::instance()->extensions()) {
if (!extension->isEnabled() || !extension->hasFileFormats())
continue;
auto formatId = extension->getCustomFormatIdForExtension(customFormatExtension,
Extension::FileFormat::Save);
if (formatId.has_value()) {
// Generate the options struct
Extension::FileFormatSaveOptions opts;
opts.canvasSize = roi.fileCanvasSize();
opts.bounds = roi.bounds();
opts.frames = roi.frames();
opts.fromFrame = roi.fromFrame();
opts.toFrame = roi.toFrame();
opts.ignoreEmptyFrames = ignoreEmptyFrames;
// TODO: ¿FramesSquence & frameBounds?
return extension->saveCustomFormat(*formatId,
filename,
context->activeDocument()->sprite(),
opts);
}
}
}
}
return false;
}
#endif
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
class SaveFileCommand : public SaveFileBaseCommand { class SaveFileCommand : public SaveFileBaseCommand {

View File

@ -18,6 +18,9 @@
#include <string> #include <string>
namespace app {
class FileOpROI;
}
namespace app { namespace app {
class Doc; class Doc;
@ -85,6 +88,13 @@ protected:
const MarkAsSaved markAsSaved, const MarkAsSaved markAsSaved,
const ResizeOnTheFly resizeOnTheFly = ResizeOnTheFly::Off, const ResizeOnTheFly resizeOnTheFly = ResizeOnTheFly::Off,
const gfx::PointF& scale = gfx::PointF(1.0, 1.0)); const gfx::PointF& scale = gfx::PointF(1.0, 1.0));
#ifdef ENABLE_SCRIPTING
bool saveCustomFormat(const Context* context,
const FileOpROI& roi,
const std::string& filename,
const std::string& filenameFormatArg,
bool ignoreEmptyFrames);
#endif
doc::FramesSequence m_framesSeq; doc::FramesSequence m_framesSeq;
bool m_adjustFramesByTag; bool m_adjustFramesByTag;

View File

@ -40,15 +40,20 @@
#include "archive.h" #include "archive.h"
#include "archive_entry.h" #include "archive_entry.h"
#include "doc.h"
#include "json11.hpp" #include "json11.hpp"
#include <fstream> #include <fstream>
#include <queue>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include "base/log.h" #include "base/log.h"
#ifdef ENABLE_SCRIPTING
#include "script/docobj.h"
#include "script/security.h"
#endif
namespace app { namespace app {
const char* Extension::kAsepriteDefaultThemeExtensionName = "aseprite-theme"; const char* Extension::kAsepriteDefaultThemeExtensionName = "aseprite-theme";
@ -358,6 +363,28 @@ void Extension::addMenuSeparator(ui::Widget* widget)
m_plugin.items.push_back(item); m_plugin.items.push_back(item);
} }
void Extension::addFileFormat(const FileFormat& format)
{
// Check for any duplicates in global extension list for both ID and file extensions.
for (const auto& extension : App::instance()->extensions()) {
if (m_fileFormats.count(format.id) > 0)
throw base::Exception("Duplicated format ID: " + format.id);
for (const auto& [id, other] : extension->m_fileFormats) {
for (const auto& otherExt : other.extensions) {
for (const auto& ourExt : format.extensions) {
if (otherExt == ourExt)
throw base::Exception(
"Extension attempting to register a custom format extension that already exists: " +
format.id + " : " + ourExt);
}
}
}
}
m_fileFormats.try_emplace(format.id, format);
}
#endif #endif
bool Extension::canBeDisabled() const bool Extension::canBeDisabled() const
@ -769,6 +796,147 @@ void Extension::addScript(const std::string& fn)
updateCategory(Category::Scripts); updateCategory(Category::Scripts);
} }
std::optional<std::string> Extension::getCustomFormatIdForExtension(
const std::string& ext,
const FileFormat::Support support) const
{
ASSERT(support > 0);
if (!hasFileFormats())
return std::nullopt;
for (const auto& [id, format] : m_fileFormats) {
for (const auto& formatExtensionString : format.extensions) {
if (formatExtensionString == ext && format.supports(support)) {
return id;
}
}
}
return std::nullopt;
}
bool Extension::loadCustomFormat(const std::string& formatId, const std::string& filename) const
{
const auto& format = m_fileFormats.at(formatId);
ASSERT(format.onsaveRef > -1);
if (!format.supports(FileFormat::Load))
return false;
script::Engine* engine = App::instance()->scriptEngine();
lua_State* L = engine->luaState();
lua_rawgeti(L, LUA_REGISTRYINDEX, format.onloadRef);
lua_pushstring(L, filename.c_str());
lua_pushcclosure(L, script::get_original_io_open(), 0);
lua_pushstring(L, filename.c_str());
lua_pushstring(L, format.binary ? "rb" : "r");
if (lua_pcall(L, 2, 1, 0) != LUA_OK) {
Console().printf("Failed to open file for reading");
return false;
}
if (lua_pcall(L, 2, 1, 0) != LUA_OK) {
Console().printf("Failed to call onload function, custom format '%s'", format.id.c_str());
return false;
}
return lua_toboolean(L, -1) > 0;
}
bool Extension::saveCustomFormat(const std::string& formatId,
const std::string& filename,
const Sprite* sprite,
const FileFormatSaveOptions& saveOptions) const
{
const auto& format = m_fileFormats.at(formatId);
ASSERT(format.onsaveRef > -1);
if (!format.supports(FileFormat::Save))
return false;
// TODO: Probably something nice to have in base, with a version that doesn't use rand() and isn't
// just copy-pasted from StackOverflow :^]
auto random_temp_file = []() {
auto randchar = []() -> char {
const char charset[] = "0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
const size_t max_index = (sizeof(charset) - 1);
return charset[rand() % max_index];
};
std::string str(12, 0);
std::generate_n(str.begin(), 12, randchar);
return "asecmf_" + str + ".tmp";
};
const std::string destTempFile = base::join_path(base::get_temp_path(), random_temp_file());
script::Engine* engine = App::instance()->scriptEngine();
lua_State* L = engine->luaState();
lua_rawgeti(L, LUA_REGISTRYINDEX, format.onsaveRef);
lua_pushstring(L, filename.c_str());
lua_pushcclosure(L, script::get_original_io_open(), 0);
lua_pushstring(L, destTempFile.c_str());
lua_pushstring(L, format.binary ? "wb" : "w");
if (lua_pcall(L, 2, 1, 0) != LUA_OK) {
Console().printf("Failed to open file for writing");
return false;
}
script::push_docobj(L, sprite);
// Creating push options table.
lua_newtable(L);
script::push_value_to_lua<gfx::Size>(L, saveOptions.canvasSize);
lua_setfield(L, -2, "canvasSize");
script::push_value_to_lua<gfx::Rect>(L, saveOptions.bounds);
lua_setfield(L, -2, "bounds");
script::setfield_integer(L, "frames", saveOptions.frames);
script::setfield_integer(L, "fromFrame", saveOptions.fromFrame);
script::setfield_integer(L, "toFrame", saveOptions.toFrame);
lua_pushboolean(L, saveOptions.ignoreEmptyFrames);
lua_setfield(L, -2, "ignoreEmptyFrames");
if (lua_pcall(L, 4, 1, 0) != LUA_OK) {
Console().printf("Failed to call onload function, custom format '%s'", format.id.c_str());
return false;
}
if (lua_toboolean(L, -1) > 0) {
// Transfer the temporary file to the actual file location
try {
// TODO: This is probably *too* cautious? I just don't want a random script we can't control
// to delete a user's files.
// TODO: Should also be moved to a job.
std::string mv;
if (base::is_file(filename)) {
mv = base::join_path(base::get_temp_path(), base::get_file_name(filename) + ".bak");
base::move_file(filename, mv);
}
base::move_file(destTempFile, filename);
if (!mv.empty())
base::delete_file(mv);
return true;
}
catch (std::exception& e) {
Console().printf("Saving failed: '%s'", e.what());
return false;
}
}
// TODO: Do we need to close the Lua file handle ourselves if something breaks?
return false;
}
#endif // ENABLE_SCRIPTING #endif // ENABLE_SCRIPTING
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -923,6 +1091,28 @@ std::vector<Extension::DitheringMatrixInfo*> Extensions::ditheringMatrices()
return result; return result;
} }
#ifdef ENABLE_SCRIPTING
std::vector<std::string> Extensions::customFormatList(
const Extension::FileFormat::Support support) const
{
std::vector<std::string> result;
for (auto* ext : m_extensions) {
if (!ext->isEnabled())
continue;
for (const auto& [id, format] : ext->m_fileFormats) {
if (!format.supports(support))
continue;
for (const auto& extension : format.extensions)
result.push_back(extension);
}
}
return result;
}
#endif
void Extensions::enableExtension(Extension* extension, const bool state) void Extensions::enableExtension(Extension* extension, const bool state)
{ {
extension->enable(state); extension->enable(state);

View File

@ -10,17 +10,24 @@
#pragma once #pragma once
#include "app/i18n/lang_info.h" #include "app/i18n/lang_info.h"
#include "doc/frame.h"
#include "gfx/rect.h"
#include "gfx/size.h"
#include "obs/signal.h" #include "obs/signal.h"
#include "render/dithering_matrix.h" #include "render/dithering_matrix.h"
#include <map> #include <map>
#include <optional>
#include <string> #include <string>
#include <unordered_set>
#include <vector> #include <vector>
namespace ui { namespace ui {
class Widget; class Widget;
} }
namespace doc {
class Sprite;
}
namespace app { namespace app {
// Key=id // Key=id
@ -28,6 +35,7 @@ namespace app {
using ExtensionItems = std::map<std::string, std::string>; using ExtensionItems = std::map<std::string, std::string>;
class Extensions; class Extensions;
class Doc;
struct ExtensionInfo { struct ExtensionInfo {
std::string name; std::string name;
@ -83,6 +91,34 @@ public:
ThemeInfo(const std::string& path, const std::string& variant) : path(path), variant(variant) {} ThemeInfo(const std::string& path, const std::string& variant) : path(path), variant(variant) {}
}; };
#ifdef ENABLE_SCRIPTING
struct FileFormat {
enum Support : uint8_t { Invalid = 0, Full, Save, Load };
std::string id;
std::unordered_set<std::string> extensions;
bool binary = true;
int onloadRef = -1;
int onsaveRef = -1;
bool supports(const Support support) const
{
if (support == Load && onloadRef > -1)
return true;
if (support == Save && onsaveRef > -1)
return true;
return support == Full && onloadRef > -1 && onsaveRef > -1;
}
};
struct FileFormatSaveOptions {
gfx::Size canvasSize;
gfx::Rect bounds;
doc::frame_t frames = 0;
doc::frame_t fromFrame = 0;
doc::frame_t toFrame = 0;
bool ignoreEmptyFrames = false;
};
#endif
using Languages = std::map<std::string, LangInfo>; using Languages = std::map<std::string, LangInfo>;
using Themes = std::map<std::string, ThemeInfo>; using Themes = std::map<std::string, ThemeInfo>;
using DitheringMatrices = std::map<std::string, DitheringMatrixInfo>; using DitheringMatrices = std::map<std::string, DitheringMatrixInfo>;
@ -122,6 +158,7 @@ public:
void removeMenuGroup(const std::string& id); void removeMenuGroup(const std::string& id);
void addMenuSeparator(ui::Widget* widget); void addMenuSeparator(ui::Widget* widget);
void addFileFormat(const FileFormat& format);
#endif #endif
bool isEnabled() const { return m_isEnabled; } bool isEnabled() const { return m_isEnabled; }
@ -137,6 +174,15 @@ public:
#ifdef ENABLE_SCRIPTING #ifdef ENABLE_SCRIPTING
bool hasScripts() const { return !m_plugin.scripts.empty(); } bool hasScripts() const { return !m_plugin.scripts.empty(); }
void addScript(const std::string& fn); void addScript(const std::string& fn);
bool hasFileFormats() const { return !m_fileFormats.empty(); }
std::optional<std::string> getCustomFormatIdForExtension(const std::string& ext,
FileFormat::Support support) const;
bool loadCustomFormat(const std::string& formatId, const std::string& filename) const;
bool saveCustomFormat(const std::string& formatId,
const std::string& filename,
const doc::Sprite* sprite,
const FileFormatSaveOptions& saveOptions) const;
#endif #endif
bool isCurrentTheme() const; bool isCurrentTheme() const;
@ -174,6 +220,7 @@ private:
std::vector<ScriptItem> scripts; std::vector<ScriptItem> scripts;
std::vector<PluginItem> items; std::vector<PluginItem> items;
} m_plugin; } m_plugin;
std::unordered_map<std::string, FileFormat> m_fileFormats;
#endif #endif
std::string m_path; std::string m_path;
@ -218,6 +265,12 @@ public:
// image file. These pointers cannot be deleted. // image file. These pointers cannot be deleted.
std::vector<Extension::DitheringMatrixInfo*> ditheringMatrices(); std::vector<Extension::DitheringMatrixInfo*> ditheringMatrices();
#ifdef ENABLE_SCRIPTING
// Returns a list of the available custom formats (by extension).
std::vector<std::string> customFormatList(
Extension::FileFormat::Support support = Extension::FileFormat::Support::Full) const;
#endif
obs::signal<void(Extension*)> NewExtension; obs::signal<void(Extension*)> NewExtension;
obs::signal<void(Extension*)> KeysChange; obs::signal<void(Extension*)> KeysChange;
obs::signal<void(Extension*)> LanguagesChange; obs::signal<void(Extension*)> LanguagesChange;

View File

@ -17,6 +17,7 @@
#include "app/context.h" #include "app/context.h"
#include "app/doc.h" #include "app/doc.h"
#include "app/drm.h" #include "app/drm.h"
#include "app/extensions.h"
#include "app/file/file_data.h" #include "app/file/file_data.h"
#include "app/file/file_format.h" #include "app/file/file_format.h"
#include "app/file/file_formats_manager.h" #include "app/file/file_formats_manager.h"
@ -193,6 +194,14 @@ base::paths get_readable_extensions()
if (format->support(FILE_SUPPORT_LOAD)) if (format->support(FILE_SUPPORT_LOAD))
format->getExtensions(paths); format->getExtensions(paths);
} }
#ifdef ENABLE_SCRIPTING
for (const auto& extension :
App::instance()->extensions().customFormatList(Extension::FileFormat::Load)) {
paths.push_back(extension);
}
#endif
return paths; return paths;
} }
@ -204,6 +213,14 @@ base::paths get_writable_extensions(const int requiredFormatFlag)
(requiredFormatFlag == 0 || format->support(requiredFormatFlag))) (requiredFormatFlag == 0 || format->support(requiredFormatFlag)))
format->getExtensions(paths); format->getExtensions(paths);
} }
#ifdef ENABLE_SCRIPTING
for (const auto& extension :
App::instance()->extensions().customFormatList(Extension::FileFormat::Load)) {
paths.push_back(extension);
}
#endif
return paths; return paths;
} }
@ -366,6 +383,7 @@ FileOp* FileOp::createLoadDocumentOperation(Context* context,
// Get the format through the extension of the filename // Get the format through the extension of the filename
fop->m_format = FileFormatsManager::instance()->getFileFormat(dio::detect_format(filename)); fop->m_format = FileFormatsManager::instance()->getFileFormat(dio::detect_format(filename));
if (!fop->m_format || !fop->m_format->support(FILE_SUPPORT_LOAD)) { if (!fop->m_format || !fop->m_format->support(FILE_SUPPORT_LOAD)) {
fop->setUnknownFormatError(true);
fop->setError("%s can't load \"%s\" file (\"%s\")\n", fop->setError("%s can't load \"%s\" file (\"%s\")\n",
get_app_name(), get_app_name(),
filename.c_str(), filename.c_str(),
@ -535,6 +553,7 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
fop->m_format = FileFormatsManager::instance()->getFileFormat( fop->m_format = FileFormatsManager::instance()->getFileFormat(
dio::detect_format_by_file_extension(filename)); dio::detect_format_by_file_extension(filename));
if (!fop->m_format || !fop->m_format->support(FILE_SUPPORT_SAVE)) { if (!fop->m_format || !fop->m_format->support(FILE_SUPPORT_SAVE)) {
fop->setUnknownFormatError(true);
fop->setError("%s can't save \"%s\" file (\"%s\")\n", fop->setError("%s can't save \"%s\" file (\"%s\")\n",
get_app_name(), get_app_name(),
filename.c_str(), filename.c_str(),
@ -1449,6 +1468,7 @@ FileOp::FileOp(FileOpType type, Context* context, const FileOpConfig* config)
, m_createPaletteFromRgba(false) , m_createPaletteFromRgba(false)
, m_ignoreEmpty(false) , m_ignoreEmpty(false)
, m_avoidBackgroundLayer(false) , m_avoidBackgroundLayer(false)
, m_unknownFormatError(false)
, m_embeddedColorProfile(false) , m_embeddedColorProfile(false)
, m_embeddedGridBounds(false) , m_embeddedGridBounds(false)
{ {

View File

@ -81,6 +81,7 @@ public:
doc::Tag* tag() const { return m_tag; } doc::Tag* tag() const { return m_tag; }
doc::frame_t fromFrame() const { return m_framesSeq.firstFrame(); } doc::frame_t fromFrame() const { return m_framesSeq.firstFrame(); }
doc::frame_t toFrame() const { return m_framesSeq.lastFrame(); } doc::frame_t toFrame() const { return m_framesSeq.lastFrame(); }
gfx::Rect bounds() const { return m_bounds; }
const doc::FramesSequence& framesSequence() const { return m_framesSeq; } const doc::FramesSequence& framesSequence() const { return m_framesSeq; }
doc::frame_t frames() const { return (doc::frame_t)m_framesSeq.size(); } doc::frame_t frames() const { return (doc::frame_t)m_framesSeq.size(); }
@ -259,6 +260,8 @@ public:
bool hasError() const { return !m_error.empty(); } bool hasError() const { return !m_error.empty(); }
void setIncompatibilityError(const std::string& msg); void setIncompatibilityError(const std::string& msg);
bool hasIncompatibilityError() const { return !m_incompatibilityError.empty(); } bool hasIncompatibilityError() const { return !m_incompatibilityError.empty(); }
void setUnknownFormatError(bool unknownFormatError) { m_unknownFormatError = unknownFormatError; }
bool hasUnknownFormatError() const { return m_unknownFormatError; }
double progress() const; double progress() const;
void setProgress(double progress); void setProgress(double progress);
@ -305,6 +308,7 @@ private:
bool m_createPaletteFromRgba; bool m_createPaletteFromRgba;
bool m_ignoreEmpty; bool m_ignoreEmpty;
bool m_avoidBackgroundLayer; bool m_avoidBackgroundLayer;
bool m_unknownFormatError;
// True if the file contained a color profile when it was loaded. // True if the file contained a color profile when it was loaded.
bool m_embeddedColorProfile; bool m_embeddedColorProfile;

View File

@ -300,6 +300,63 @@ int Plugin_newMenuSeparator(lua_State* L)
return 0; return 0;
} }
int Plugin_newFileFormat(lua_State* L)
{
auto* plugin = get_obj<Plugin>(L, 1);
if (!lua_istable(L, 2))
return luaL_error(L,
"plugin:newFileFormat() must be called with a table as its first argument");
Extension::FileFormat format;
int type = lua_getfield(L, 2, "id");
if (type == LUA_TSTRING) {
format.id = lua_tostring(L, -1);
if (format.id.empty())
return luaL_error(L, "format id is invalid or empty");
}
else
return luaL_error(L, "format id must be a string");
lua_pop(L, 1);
type = lua_getfield(L, 2, "binary");
if (type == LUA_TBOOLEAN) {
format.binary = lua_toboolean(L, -1);
lua_pop(L, 1);
}
type = lua_getfield(L, 2, "extensions");
if (type == LUA_TTABLE) {
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
format.extensions.emplace(base::string_to_lower(luaL_tolstring(L, -1, nullptr)));
lua_pop(L, 2);
}
if (format.extensions.empty())
return luaL_error(L, "format extension list is empty");
}
else
return luaL_error(L, "format extension list must be a table");
lua_pop(L, 1);
type = lua_getfield(L, 2, "onload");
if (type == LUA_TFUNCTION)
format.onloadRef = luaL_ref(L, LUA_REGISTRYINDEX);
else
return luaL_error(L, "format must have an onload function"); // TODO: Make either of these
// optional, so we can only read
// and not write and vice-versa.
type = lua_getfield(L, 2, "onsave");
if (type == LUA_TFUNCTION)
format.onsaveRef = luaL_ref(L, LUA_REGISTRYINDEX);
else
return luaL_error(L, "format must have an onsave function");
plugin->ext->addFileFormat(format);
return 0;
}
int Plugin_get_name(lua_State* L) int Plugin_get_name(lua_State* L)
{ {
auto* plugin = get_obj<Plugin>(L, 1); auto* plugin = get_obj<Plugin>(L, 1);
@ -353,6 +410,7 @@ const luaL_Reg Plugin_methods[] = {
{ "newMenuGroup", Plugin_newMenuGroup }, { "newMenuGroup", Plugin_newMenuGroup },
{ "deleteMenuGroup", Plugin_deleteMenuGroup }, { "deleteMenuGroup", Plugin_deleteMenuGroup },
{ "newMenuSeparator", Plugin_newMenuSeparator }, { "newMenuSeparator", Plugin_newMenuSeparator },
{ "newFileFormat", Plugin_newFileFormat },
{ nullptr, nullptr } { nullptr, nullptr }
}; };

View File

@ -441,4 +441,9 @@ bool ask_access(lua_State* L,
return true; return true;
} }
lua_CFunction get_original_io_open()
{
return replaced_functions[io_open].origfunc;
}
}} // namespace app::script }} // namespace app::script

View File

@ -14,6 +14,7 @@
#endif #endif
#include "app/script/engine.h" #include "app/script/engine.h"
#include "lua.h"
namespace app { namespace script { namespace app { namespace script {
@ -35,6 +36,8 @@ enum class ResourceType {
void overwrite_unsecure_functions(lua_State* L); void overwrite_unsecure_functions(lua_State* L);
lua_CFunction get_original_io_open();
bool ask_access(lua_State* L, bool ask_access(lua_State* L,
const char* filename, const char* filename,
const FileAccessMode mode, const FileAccessMode mode,