Files
panopainter/src/app_core/document_export.h

1146 lines
39 KiB
C++

#pragma once
#include "app_core/app_dialog.h"
#include "app_core/document_canvas.h"
#include "document/document.h"
#include "foundation/result.h"
#include <array>
#include <cstddef>
#include <span>
#include <string>
#include <string_view>
#include <utility>
namespace pp::app {
struct DocumentExportFileTarget {
std::string path;
std::string suggested_name;
};
struct DocumentExportCollectionTarget {
std::string directory;
std::string stem_path;
};
struct DocumentExportStemTarget {
std::string stem_path;
};
struct DocumentCubeFaceExportFileTarget {
std::string face_name;
std::string path;
};
struct DocumentCubeFaceExportTarget {
std::array<DocumentCubeFaceExportFileTarget, pp::document::cube_face_count> faces;
std::size_t face_count = 0;
};
struct DocumentDepthExportTarget {
std::string image_path;
std::string depth_path;
};
struct DocumentCubeFaceExportPayload {
std::span<const std::byte> bytes;
};
struct DocumentDepthExportPayload {
std::span<const std::byte> image_bytes;
std::span<const std::byte> depth_bytes;
};
struct DocumentExportFilePayload {
std::span<const std::byte> bytes;
};
struct DocumentExportCollectionPngPayload {
std::string path_suffix;
std::span<const std::byte> bytes;
};
struct DocumentExportSuggestedName {
std::string name;
};
enum class DocumentExportCollectionDestination {
work_directory_collection,
picked_directory_stem,
};
enum class DocumentExportStartDecision {
start_now,
show_license_disabled,
unavailable_no_canvas,
};
enum class DocumentExportMenuKind {
jpeg,
png,
layers,
cube_faces,
depth,
animation_frames,
animation_mp4,
timelapse,
};
enum class DocumentExportMenuAction {
show_jpeg_dialog,
show_png_dialog,
show_layers_dialog,
show_cube_faces_dialog,
show_depth_dialog,
show_animation_frames_dialog,
show_animation_mp4_dialog,
show_timelapse_dialog,
show_license_disabled,
unavailable_no_canvas,
};
enum class DocumentExportCollectionKind {
layers,
animation_frames,
};
enum class DocumentVideoExportKind {
animation_mp4,
timelapse,
};
enum class DocumentExportSuccessKind {
equirectangular,
layers,
animation_frames,
depth,
cube_faces,
animation_mp4,
timelapse,
};
enum class DocumentExportSuccessDestination {
suppressed,
photos,
pictures_panopainter,
files_panopainter,
work_directory,
path,
generic_success,
};
enum class DocumentExportExecutionKind {
equirectangular_file,
layers_collection,
layers_stem,
animation_frames_collection,
animation_frames_stem,
depth,
cube_faces,
animation_mp4,
timelapse,
};
enum class DocumentExportSnapshotRouteAction {
use_document_snapshot_writer,
use_legacy_export,
};
struct DocumentExportMenuPlan {
DocumentExportMenuKind kind = DocumentExportMenuKind::jpeg;
DocumentExportMenuAction action = DocumentExportMenuAction::show_jpeg_dialog;
bool opens_dialog = true;
};
struct DocumentExportCollectionTargetPlan {
DocumentExportCollectionKind kind = DocumentExportCollectionKind::layers;
DocumentExportCollectionDestination destination = DocumentExportCollectionDestination::picked_directory_stem;
std::string_view suffix;
};
struct DocumentExportSuccessDialogPlan {
DocumentExportSuccessKind kind = DocumentExportSuccessKind::equirectangular;
DocumentExportSuccessDestination destination = DocumentExportSuccessDestination::suppressed;
AppMessageDialogPlan dialog;
bool show_dialog = false;
};
struct DocumentExportSnapshotRoutePlan {
DocumentExportExecutionKind kind = DocumentExportExecutionKind::equirectangular_file;
DocumentExportSnapshotRouteAction action = DocumentExportSnapshotRouteAction::use_legacy_export;
bool payload_complete = false;
bool target_supported = false;
bool platform_supported = false;
bool uses_document_snapshot_writer = false;
std::string_view fallback_reason;
};
class DocumentExportMenuServices {
public:
virtual ~DocumentExportMenuServices() = default;
virtual void show_jpeg_dialog() = 0;
virtual void show_png_dialog() = 0;
virtual void show_layers_dialog() = 0;
virtual void show_cube_faces_dialog() = 0;
virtual void show_depth_dialog() = 0;
virtual void show_animation_frames_dialog() = 0;
virtual void show_animation_mp4_dialog() = 0;
virtual void show_timelapse_dialog() = 0;
virtual void show_license_disabled() = 0;
};
class DocumentExportServices {
public:
virtual ~DocumentExportServices() = default;
virtual bool create_directory(std::string_view directory) = 0;
virtual void export_equirectangular(const DocumentExportFileTarget& target) = 0;
virtual void export_layers_to_stem(const DocumentExportStemTarget& target) = 0;
virtual void export_layers_to_collection(const DocumentExportCollectionTarget& target) = 0;
virtual void export_animation_frames_to_stem(const DocumentExportStemTarget& target) = 0;
virtual void export_animation_frames_to_collection(const DocumentExportCollectionTarget& target) = 0;
virtual void export_depth(std::string_view document_name) = 0;
virtual void export_cube_faces(std::string_view document_name) = 0;
};
class DocumentVideoExportServices {
public:
virtual ~DocumentVideoExportServices() = default;
virtual void export_animation_mp4(std::string_view path) = 0;
virtual void export_timelapse_mp4(std::string_view path) = 0;
virtual void show_animation_export_success(std::string_view path) = 0;
virtual void show_timelapse_export_success(std::string_view path) = 0;
};
class DocumentCubeFaceExportWriteServices {
public:
virtual ~DocumentCubeFaceExportWriteServices() = default;
virtual pp::foundation::Status write_binary_file(
std::string_view path,
std::span<const std::byte> bytes) = 0;
virtual void publish_exported_image(std::string_view path) = 0;
};
class DocumentDepthExportWriteServices {
public:
virtual ~DocumentDepthExportWriteServices() = default;
virtual pp::foundation::Status write_binary_file(
std::string_view path,
std::span<const std::byte> bytes) = 0;
virtual void publish_exported_image(std::string_view path) = 0;
};
class DocumentExportFileWriteServices {
public:
virtual ~DocumentExportFileWriteServices() = default;
virtual pp::foundation::Status write_binary_file(
std::string_view path,
std::span<const std::byte> bytes) = 0;
virtual void publish_exported_image(std::string_view path) = 0;
};
class DocumentExportCollectionWriteServices {
public:
virtual ~DocumentExportCollectionWriteServices() = default;
virtual pp::foundation::Status write_binary_file(
std::string_view path,
std::span<const std::byte> bytes) = 0;
virtual void publish_exported_image(std::string_view path) = 0;
};
[[nodiscard]] constexpr DocumentExportStartDecision plan_document_export_start(
bool requires_license,
bool license_valid,
bool has_canvas) noexcept
{
if (requires_license && !license_valid) {
return DocumentExportStartDecision::show_license_disabled;
}
return has_canvas
? DocumentExportStartDecision::start_now
: DocumentExportStartDecision::unavailable_no_canvas;
}
[[nodiscard]] constexpr bool document_export_menu_requires_license(
DocumentExportMenuKind kind) noexcept
{
switch (kind) {
case DocumentExportMenuKind::animation_mp4:
case DocumentExportMenuKind::timelapse:
return true;
case DocumentExportMenuKind::jpeg:
case DocumentExportMenuKind::png:
case DocumentExportMenuKind::layers:
case DocumentExportMenuKind::cube_faces:
case DocumentExportMenuKind::depth:
case DocumentExportMenuKind::animation_frames:
return false;
}
return false;
}
[[nodiscard]] constexpr DocumentExportMenuAction document_export_menu_dialog_action(
DocumentExportMenuKind kind) noexcept
{
switch (kind) {
case DocumentExportMenuKind::jpeg:
return DocumentExportMenuAction::show_jpeg_dialog;
case DocumentExportMenuKind::png:
return DocumentExportMenuAction::show_png_dialog;
case DocumentExportMenuKind::layers:
return DocumentExportMenuAction::show_layers_dialog;
case DocumentExportMenuKind::cube_faces:
return DocumentExportMenuAction::show_cube_faces_dialog;
case DocumentExportMenuKind::depth:
return DocumentExportMenuAction::show_depth_dialog;
case DocumentExportMenuKind::animation_frames:
return DocumentExportMenuAction::show_animation_frames_dialog;
case DocumentExportMenuKind::animation_mp4:
return DocumentExportMenuAction::show_animation_mp4_dialog;
case DocumentExportMenuKind::timelapse:
return DocumentExportMenuAction::show_timelapse_dialog;
}
return DocumentExportMenuAction::show_jpeg_dialog;
}
[[nodiscard]] constexpr DocumentExportSuccessDestination document_export_equirectangular_platform_destination() noexcept
{
#if defined(__IOS__)
return DocumentExportSuccessDestination::photos;
#elif defined(__OSX__)
return DocumentExportSuccessDestination::pictures_panopainter;
#elif defined(_WIN32)
return DocumentExportSuccessDestination::work_directory;
#else
return DocumentExportSuccessDestination::suppressed;
#endif
}
[[nodiscard]] constexpr DocumentExportSuccessDestination document_export_media_platform_destination() noexcept
{
#if defined(__IOS__)
return DocumentExportSuccessDestination::files_panopainter;
#elif defined(__OSX__)
return DocumentExportSuccessDestination::pictures_panopainter;
#elif defined(_WIN32)
return DocumentExportSuccessDestination::work_directory;
#else
return DocumentExportSuccessDestination::suppressed;
#endif
}
[[nodiscard]] constexpr DocumentExportMenuPlan plan_document_export_menu_action(
DocumentExportMenuKind kind,
bool has_canvas,
bool license_valid) noexcept
{
DocumentExportMenuPlan plan;
plan.kind = kind;
plan.action = document_export_menu_dialog_action(kind);
const auto start = plan_document_export_start(
document_export_menu_requires_license(kind),
license_valid,
has_canvas);
if (start == DocumentExportStartDecision::show_license_disabled) {
plan.action = DocumentExportMenuAction::show_license_disabled;
plan.opens_dialog = false;
} else if (start == DocumentExportStartDecision::unavailable_no_canvas) {
plan.action = DocumentExportMenuAction::unavailable_no_canvas;
plan.opens_dialog = false;
}
return plan;
}
[[nodiscard]] constexpr std::string_view document_export_collection_suffix(
DocumentExportCollectionKind kind) noexcept
{
switch (kind) {
case DocumentExportCollectionKind::layers:
return "_layers";
case DocumentExportCollectionKind::animation_frames:
return "_frames";
}
return {};
}
[[nodiscard]] constexpr DocumentExportSuccessKind document_export_collection_success_kind(
DocumentExportCollectionKind kind) noexcept
{
switch (kind) {
case DocumentExportCollectionKind::layers:
return DocumentExportSuccessKind::layers;
case DocumentExportCollectionKind::animation_frames:
return DocumentExportSuccessKind::animation_frames;
}
return DocumentExportSuccessKind::layers;
}
[[nodiscard]] constexpr const char* document_export_failure_dialog_title(
DocumentExportSuccessKind kind) noexcept
{
switch (kind) {
case DocumentExportSuccessKind::equirectangular:
return "Export Equirectangular";
case DocumentExportSuccessKind::layers:
case DocumentExportSuccessKind::animation_frames:
return "Export Layers";
case DocumentExportSuccessKind::depth:
return "Export 3D View + Depth";
case DocumentExportSuccessKind::cube_faces:
return "Export Cube Faces";
case DocumentExportSuccessKind::animation_mp4:
return "Export Animation";
case DocumentExportSuccessKind::timelapse:
return "Export Timelapse";
}
return "Export";
}
[[nodiscard]] constexpr const char* document_export_execution_log_message(
DocumentExportExecutionKind kind) noexcept
{
switch (kind) {
case DocumentExportExecutionKind::equirectangular_file:
return "Document export file action failed";
case DocumentExportExecutionKind::layers_collection:
return "Document layer collection export failed";
case DocumentExportExecutionKind::layers_stem:
return "Document layer stem export failed";
case DocumentExportExecutionKind::animation_frames_collection:
return "Document animation frame collection export failed";
case DocumentExportExecutionKind::animation_frames_stem:
return "Document animation frame stem export failed";
case DocumentExportExecutionKind::depth:
return "Document depth export failed";
case DocumentExportExecutionKind::cube_faces:
return "Document cube-face export failed";
case DocumentExportExecutionKind::animation_mp4:
return "Document animation export failed";
case DocumentExportExecutionKind::timelapse:
return "Document timelapse export failed";
}
return "Document export failed";
}
[[nodiscard]] constexpr bool ascii_iequals(std::string_view left, std::string_view right) noexcept
{
if (left.size() != right.size()) {
return false;
}
for (std::size_t i = 0; i < left.size(); ++i) {
auto lhs = left[i];
if (lhs >= 'A' && lhs <= 'Z') {
lhs = static_cast<char>(lhs - 'A' + 'a');
}
auto rhs = right[i];
if (rhs >= 'A' && rhs <= 'Z') {
rhs = static_cast<char>(rhs - 'A' + 'a');
}
if (lhs != rhs) {
return false;
}
}
return true;
}
[[nodiscard]] constexpr bool document_export_path_has_extension(
std::string_view path,
std::string_view extension) noexcept
{
return path.size() >= extension.size()
&& ascii_iequals(path.substr(path.size() - extension.size()), extension);
}
[[nodiscard]] constexpr bool document_export_path_is_png_target(std::string_view path) noexcept
{
return document_export_path_has_extension(path, ".png");
}
[[nodiscard]] constexpr bool document_export_path_is_jpeg_target(std::string_view path) noexcept
{
return document_export_path_has_extension(path, ".jpg")
|| document_export_path_has_extension(path, ".jpeg");
}
[[nodiscard]] constexpr bool document_export_snapshot_target_supported(
DocumentExportExecutionKind kind,
std::string_view target_path = {}) noexcept
{
switch (kind) {
case DocumentExportExecutionKind::equirectangular_file:
return document_export_path_is_png_target(target_path)
|| document_export_path_is_jpeg_target(target_path);
case DocumentExportExecutionKind::layers_collection:
case DocumentExportExecutionKind::layers_stem:
case DocumentExportExecutionKind::animation_frames_collection:
case DocumentExportExecutionKind::animation_frames_stem:
case DocumentExportExecutionKind::depth:
case DocumentExportExecutionKind::cube_faces:
return true;
case DocumentExportExecutionKind::animation_mp4:
case DocumentExportExecutionKind::timelapse:
return false;
}
return false;
}
[[nodiscard]] constexpr bool document_export_snapshot_platform_supported() noexcept
{
#if __WEB__
return false;
#else
return true;
#endif
}
[[nodiscard]] constexpr DocumentExportSnapshotRoutePlan plan_document_export_snapshot_route(
DocumentExportExecutionKind kind,
DocumentCanvasSaveSnapshotReport report,
bool target_supported,
bool platform_supported) noexcept
{
DocumentExportSnapshotRoutePlan plan;
plan.kind = kind;
plan.payload_complete = report.payload_complete;
plan.target_supported = target_supported;
plan.platform_supported = platform_supported;
if (!platform_supported) {
plan.fallback_reason = "document snapshot export is disabled on this platform";
return plan;
}
if (!target_supported) {
plan.fallback_reason = "document snapshot export does not support this target";
return plan;
}
if (!report.payload_complete) {
plan.fallback_reason = "document snapshot still requires renderer payload readback";
return plan;
}
plan.action = DocumentExportSnapshotRouteAction::use_document_snapshot_writer;
plan.uses_document_snapshot_writer = true;
return plan;
}
[[nodiscard]] constexpr DocumentExportSnapshotRoutePlan plan_document_export_snapshot_route_for_target(
DocumentExportExecutionKind kind,
DocumentCanvasSaveSnapshotReport report,
std::string_view target_path,
bool platform_supported) noexcept
{
return plan_document_export_snapshot_route(
kind,
report,
document_export_snapshot_target_supported(kind, target_path),
platform_supported);
}
[[nodiscard]] constexpr DocumentExportSnapshotRoutePlan plan_document_export_snapshot_route_for_current_platform(
DocumentExportExecutionKind kind,
DocumentCanvasSaveSnapshotReport report,
std::string_view target_path = {}) noexcept
{
return plan_document_export_snapshot_route_for_target(
kind,
report,
target_path,
document_export_snapshot_platform_supported());
}
[[nodiscard]] constexpr DocumentExportCollectionTargetPlan plan_document_export_collection_target(
DocumentExportCollectionKind kind,
bool use_work_directory_collection) noexcept
{
return {
kind,
use_work_directory_collection
? DocumentExportCollectionDestination::work_directory_collection
: DocumentExportCollectionDestination::picked_directory_stem,
document_export_collection_suffix(kind),
};
}
[[nodiscard]] inline AppMessageDialogPlan plan_document_export_failure_dialog(
DocumentExportSuccessKind kind,
std::string_view status_message)
{
return plan_app_message_dialog(
document_export_failure_dialog_title(kind),
status_message,
false);
}
[[nodiscard]] inline AppMessageDialogPlan plan_document_export_license_disabled_dialog()
{
return plan_app_message_dialog(
"License",
"This function is disabled in demo mode.",
false);
}
[[nodiscard]] inline DocumentExportSuccessDialogPlan plan_document_export_success_dialog(
DocumentExportSuccessKind kind,
DocumentExportSuccessDestination destination,
std::string_view detail = {})
{
DocumentExportSuccessDialogPlan plan;
plan.kind = kind;
plan.destination = destination;
if (destination == DocumentExportSuccessDestination::suppressed) {
return plan;
}
std::string message;
switch (kind) {
case DocumentExportSuccessKind::equirectangular:
plan.dialog.title = "Export Equirectangular";
switch (destination) {
case DocumentExportSuccessDestination::photos:
message = "Image exported to Photos";
break;
case DocumentExportSuccessDestination::pictures_panopainter:
message = "Image exported to Pictures/PanoPainter folder";
break;
case DocumentExportSuccessDestination::work_directory:
message = "Image exported to ";
message += detail;
break;
case DocumentExportSuccessDestination::suppressed:
case DocumentExportSuccessDestination::files_panopainter:
case DocumentExportSuccessDestination::path:
case DocumentExportSuccessDestination::generic_success:
break;
}
break;
case DocumentExportSuccessKind::layers:
case DocumentExportSuccessKind::animation_frames:
plan.dialog.title = "Export Layers";
if (destination == DocumentExportSuccessDestination::files_panopainter) {
message = "Image layers exported to Files/PanoPainter";
} else if (destination == DocumentExportSuccessDestination::path) {
message = "Layers exported to: ";
message += detail;
}
break;
case DocumentExportSuccessKind::depth:
plan.dialog.title = "Export 3D View + Depth";
if (destination == DocumentExportSuccessDestination::files_panopainter) {
message = "Image and depth exported to Files/PanoPainter";
} else if (destination == DocumentExportSuccessDestination::pictures_panopainter) {
message = "Image and depth exported to Pictures/PanoPainter folder";
} else if (destination == DocumentExportSuccessDestination::work_directory) {
message = "Image and depth exported to ";
message += detail;
}
break;
case DocumentExportSuccessKind::cube_faces:
plan.dialog.title = "Export Cube Faces";
if (destination == DocumentExportSuccessDestination::files_panopainter) {
message = "Image and depth exported to Files/PanoPainter";
} else if (destination == DocumentExportSuccessDestination::pictures_panopainter) {
message = "Image and depth exported to Pictures/PanoPainter folder";
} else if (destination == DocumentExportSuccessDestination::work_directory) {
message = "Image and depth exported to ";
message += detail;
}
break;
case DocumentExportSuccessKind::animation_mp4:
plan.dialog.title = "Export Animation";
if (destination == DocumentExportSuccessDestination::path) {
message = "Animation exported to: ";
message += detail;
} else if (destination == DocumentExportSuccessDestination::generic_success) {
message = "Animation exported successfully.";
}
break;
case DocumentExportSuccessKind::timelapse:
plan.dialog.title = "Export Timelapse";
if (destination == DocumentExportSuccessDestination::path) {
message = "Timelapse exported to: ";
message += detail;
} else if (destination == DocumentExportSuccessDestination::generic_success) {
message = "Timelapse exported successfully.";
}
break;
}
if (!plan.dialog.title.empty() && !message.empty()) {
plan.dialog.message = std::move(message);
plan.show_dialog = true;
}
return plan;
}
[[nodiscard]] inline pp::foundation::Result<DocumentExportFileTarget> make_document_export_file_target(
std::string_view work_directory,
std::string_view document_name,
std::string_view extension)
{
if (document_name.empty()) {
return pp::foundation::Result<DocumentExportFileTarget>::failure(
pp::foundation::Status::invalid_argument("document name must not be empty"));
}
if (extension.empty()) {
return pp::foundation::Result<DocumentExportFileTarget>::failure(
pp::foundation::Status::invalid_argument("extension must not be empty"));
}
DocumentExportFileTarget target;
target.suggested_name.reserve(document_name.size() + extension.size());
target.suggested_name += document_name;
target.suggested_name += extension;
target.path.reserve(work_directory.size() + target.suggested_name.size() + 1);
target.path += work_directory;
target.path += "/";
target.path += target.suggested_name;
return pp::foundation::Result<DocumentExportFileTarget>::success(std::move(target));
}
[[nodiscard]] inline pp::foundation::Result<DocumentExportCollectionTarget> make_document_export_collection_target(
std::string_view work_directory,
std::string_view document_name,
std::string_view suffix)
{
if (document_name.empty()) {
return pp::foundation::Result<DocumentExportCollectionTarget>::failure(
pp::foundation::Status::invalid_argument("document name must not be empty"));
}
DocumentExportCollectionTarget target;
target.directory.reserve(work_directory.size() + document_name.size() + suffix.size() + 1);
target.directory += work_directory;
target.directory += "/";
target.directory += document_name;
target.directory += suffix;
target.stem_path.reserve(target.directory.size() + document_name.size() + 1);
target.stem_path += target.directory;
target.stem_path += "/";
target.stem_path += document_name;
return pp::foundation::Result<DocumentExportCollectionTarget>::success(std::move(target));
}
[[nodiscard]] inline pp::foundation::Result<DocumentExportStemTarget> make_document_export_stem_target(
std::string_view directory,
std::string_view document_name)
{
if (document_name.empty()) {
return pp::foundation::Result<DocumentExportStemTarget>::failure(
pp::foundation::Status::invalid_argument("document name must not be empty"));
}
DocumentExportStemTarget target;
target.stem_path.reserve(directory.size() + document_name.size() + 1);
target.stem_path += directory;
target.stem_path += "/";
target.stem_path += document_name;
return pp::foundation::Result<DocumentExportStemTarget>::success(std::move(target));
}
[[nodiscard]] constexpr std::array<std::string_view, pp::document::cube_face_count>
document_cube_face_export_names() noexcept
{
return {
"front",
"right",
"back",
"left",
"top",
"bottom",
};
}
[[nodiscard]] inline pp::foundation::Result<DocumentCubeFaceExportTarget> make_document_cube_face_export_target(
std::string_view work_directory,
std::string_view document_name)
{
if (work_directory.empty()) {
return pp::foundation::Result<DocumentCubeFaceExportTarget>::failure(
pp::foundation::Status::invalid_argument("work directory must not be empty"));
}
if (document_name.empty()) {
return pp::foundation::Result<DocumentCubeFaceExportTarget>::failure(
pp::foundation::Status::invalid_argument("document name must not be empty"));
}
DocumentCubeFaceExportTarget target;
const auto face_names = document_cube_face_export_names();
target.face_count = face_names.size();
for (std::size_t face_index = 0; face_index < face_names.size(); ++face_index) {
auto& face = target.faces[face_index];
face.face_name = face_names[face_index];
face.path.reserve(work_directory.size() + document_name.size() + face_names[face_index].size() + 6);
face.path += work_directory;
face.path += "/";
face.path += document_name;
face.path += "-";
face.path += face_names[face_index];
face.path += ".png";
}
return pp::foundation::Result<DocumentCubeFaceExportTarget>::success(std::move(target));
}
[[nodiscard]] inline pp::foundation::Result<DocumentDepthExportTarget> make_document_depth_export_target(
std::string_view work_directory,
std::string_view document_name)
{
if (work_directory.empty()) {
return pp::foundation::Result<DocumentDepthExportTarget>::failure(
pp::foundation::Status::invalid_argument("work directory must not be empty"));
}
if (document_name.empty()) {
return pp::foundation::Result<DocumentDepthExportTarget>::failure(
pp::foundation::Status::invalid_argument("document name must not be empty"));
}
DocumentDepthExportTarget target;
target.image_path.reserve(work_directory.size() + document_name.size() + 5U);
target.image_path += work_directory;
target.image_path += "/";
target.image_path += document_name;
target.image_path += ".png";
target.depth_path.reserve(work_directory.size() + document_name.size() + 11U);
target.depth_path += work_directory;
target.depth_path += "/";
target.depth_path += document_name;
target.depth_path += "_depth.png";
return pp::foundation::Result<DocumentDepthExportTarget>::success(std::move(target));
}
[[nodiscard]] inline std::string document_export_two_digit_index(std::size_t index)
{
auto value = std::to_string(index);
if (value.size() < 2U) {
value.insert(value.begin(), '0');
}
return value;
}
[[nodiscard]] inline std::string make_document_layer_export_path_suffix(
std::size_t layer_index,
std::string_view layer_name)
{
std::string suffix;
const auto index = document_export_two_digit_index(layer_index);
suffix.reserve(10U + index.size() + layer_name.size());
suffix += "-layer";
suffix += index;
suffix += "-";
suffix += layer_name;
suffix += ".png";
return suffix;
}
[[nodiscard]] inline std::string make_document_animation_frame_export_path_suffix(std::size_t frame_index)
{
std::string suffix;
const auto index = document_export_two_digit_index(frame_index);
suffix.reserve(5U + index.size());
suffix += "-";
suffix += index;
suffix += ".png";
return suffix;
}
[[nodiscard]] inline pp::foundation::Result<DocumentExportSuggestedName> make_document_export_suggested_name(
std::string_view document_name,
std::string_view suffix)
{
if (document_name.empty()) {
return pp::foundation::Result<DocumentExportSuggestedName>::failure(
pp::foundation::Status::invalid_argument("document name must not be empty"));
}
DocumentExportSuggestedName target;
target.name.reserve(document_name.size() + suffix.size());
target.name += document_name;
target.name += suffix;
return pp::foundation::Result<DocumentExportSuggestedName>::success(std::move(target));
}
[[nodiscard]] inline pp::foundation::Status execute_document_cube_face_export_write(
const DocumentCubeFaceExportTarget& target,
std::span<const DocumentCubeFaceExportPayload> face_payloads,
DocumentCubeFaceExportWriteServices& services)
{
if (target.face_count != pp::document::cube_face_count) {
return pp::foundation::Status::invalid_argument("cube face export target must contain all cube faces");
}
if (face_payloads.size() != target.face_count) {
return pp::foundation::Status::invalid_argument("cube face export payload count must match target face count");
}
for (std::size_t face_index = 0; face_index < target.face_count; ++face_index) {
const auto& face = target.faces[face_index];
if (face.path.empty()) {
return pp::foundation::Status::invalid_argument("cube face export path must not be empty");
}
if (face_payloads[face_index].bytes.empty()) {
return pp::foundation::Status::invalid_argument("cube face export payload must not be empty");
}
const auto write_status = services.write_binary_file(face.path, face_payloads[face_index].bytes);
if (!write_status.ok()) {
return write_status;
}
services.publish_exported_image(face.path);
}
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status execute_document_depth_export_write(
const DocumentDepthExportTarget& target,
DocumentDepthExportPayload payload,
DocumentDepthExportWriteServices& services)
{
if (target.image_path.empty() || target.depth_path.empty()) {
return pp::foundation::Status::invalid_argument("depth export target requires image and depth paths");
}
if (payload.image_bytes.empty() || payload.depth_bytes.empty()) {
return pp::foundation::Status::invalid_argument("depth export payload requires image and depth bytes");
}
const auto image_status = services.write_binary_file(target.image_path, payload.image_bytes);
if (!image_status.ok()) {
return image_status;
}
services.publish_exported_image(target.image_path);
const auto depth_status = services.write_binary_file(target.depth_path, payload.depth_bytes);
if (!depth_status.ok()) {
return depth_status;
}
services.publish_exported_image(target.depth_path);
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status execute_document_export_file_write(
const DocumentExportFileTarget& target,
DocumentExportFilePayload payload,
DocumentExportFileWriteServices& services)
{
if (target.path.empty()) {
return pp::foundation::Status::invalid_argument("export file target requires a path");
}
if (payload.bytes.empty()) {
return pp::foundation::Status::invalid_argument("export file payload must not be empty");
}
const auto write_status = services.write_binary_file(target.path, payload.bytes);
if (!write_status.ok()) {
return write_status;
}
services.publish_exported_image(target.path);
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status execute_document_export_collection_write(
const DocumentExportCollectionTarget& target,
std::span<const DocumentExportCollectionPngPayload> payloads,
DocumentExportCollectionWriteServices& services)
{
if (target.stem_path.empty()) {
return pp::foundation::Status::invalid_argument("export collection target requires a stem path");
}
if (payloads.empty()) {
return pp::foundation::Status::invalid_argument("export collection payloads must not be empty");
}
for (const auto& payload : payloads) {
if (payload.path_suffix.empty()) {
return pp::foundation::Status::invalid_argument("export collection payload suffix must not be empty");
}
if (payload.bytes.empty()) {
return pp::foundation::Status::invalid_argument("export collection payload must not be empty");
}
std::string path;
path.reserve(target.stem_path.size() + payload.path_suffix.size());
path += target.stem_path;
path += payload.path_suffix;
const auto write_status = services.write_binary_file(path, payload.bytes);
if (!write_status.ok()) {
return write_status;
}
services.publish_exported_image(path);
}
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status execute_document_export_file(
const DocumentExportFileTarget& target,
DocumentExportServices& services)
{
if (target.path.empty() || target.suggested_name.empty()) {
return pp::foundation::Status::invalid_argument("export file target requires a path and suggested name");
}
services.export_equirectangular(target);
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status execute_document_export_stem(
DocumentExportCollectionKind kind,
const DocumentExportStemTarget& target,
DocumentExportServices& services)
{
if (target.stem_path.empty()) {
return pp::foundation::Status::invalid_argument("export stem target requires a stem path");
}
switch (kind) {
case DocumentExportCollectionKind::layers:
services.export_layers_to_stem(target);
return pp::foundation::Status::success();
case DocumentExportCollectionKind::animation_frames:
services.export_animation_frames_to_stem(target);
return pp::foundation::Status::success();
}
return pp::foundation::Status::invalid_argument("unknown document export collection kind");
}
[[nodiscard]] inline pp::foundation::Status execute_document_export_collection(
DocumentExportCollectionKind kind,
const DocumentExportCollectionTarget& target,
DocumentExportServices& services)
{
if (target.directory.empty() || target.stem_path.empty()) {
return pp::foundation::Status::invalid_argument("export collection target requires a directory and stem path");
}
if (!services.create_directory(target.directory)) {
return pp::foundation::Status::success();
}
switch (kind) {
case DocumentExportCollectionKind::layers:
services.export_layers_to_collection(target);
return pp::foundation::Status::success();
case DocumentExportCollectionKind::animation_frames:
services.export_animation_frames_to_collection(target);
return pp::foundation::Status::success();
}
return pp::foundation::Status::invalid_argument("unknown document export collection kind");
}
[[nodiscard]] inline pp::foundation::Status execute_document_export_depth(
std::string_view document_name,
DocumentExportServices& services)
{
if (document_name.empty()) {
return pp::foundation::Status::invalid_argument("document name must not be empty");
}
services.export_depth(document_name);
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status execute_document_export_cube_faces(
std::string_view document_name,
DocumentExportServices& services)
{
if (document_name.empty()) {
return pp::foundation::Status::invalid_argument("document name must not be empty");
}
services.export_cube_faces(document_name);
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Status execute_document_video_export(
DocumentVideoExportKind kind,
std::string_view path,
DocumentVideoExportServices& services)
{
if (path.empty()) {
return pp::foundation::Status::invalid_argument("video export path must not be empty");
}
switch (kind) {
case DocumentVideoExportKind::animation_mp4:
services.export_animation_mp4(path);
services.show_animation_export_success(path);
return pp::foundation::Status::success();
case DocumentVideoExportKind::timelapse:
services.export_timelapse_mp4(path);
services.show_timelapse_export_success(path);
return pp::foundation::Status::success();
}
return pp::foundation::Status::invalid_argument("unknown document video export kind");
}
[[nodiscard]] inline pp::foundation::Status execute_document_export_menu_plan(
const DocumentExportMenuPlan& plan,
DocumentExportMenuServices& services)
{
switch (plan.action) {
case DocumentExportMenuAction::show_jpeg_dialog:
services.show_jpeg_dialog();
return pp::foundation::Status::success();
case DocumentExportMenuAction::show_png_dialog:
services.show_png_dialog();
return pp::foundation::Status::success();
case DocumentExportMenuAction::show_layers_dialog:
services.show_layers_dialog();
return pp::foundation::Status::success();
case DocumentExportMenuAction::show_cube_faces_dialog:
services.show_cube_faces_dialog();
return pp::foundation::Status::success();
case DocumentExportMenuAction::show_depth_dialog:
services.show_depth_dialog();
return pp::foundation::Status::success();
case DocumentExportMenuAction::show_animation_frames_dialog:
services.show_animation_frames_dialog();
return pp::foundation::Status::success();
case DocumentExportMenuAction::show_animation_mp4_dialog:
services.show_animation_mp4_dialog();
return pp::foundation::Status::success();
case DocumentExportMenuAction::show_timelapse_dialog:
services.show_timelapse_dialog();
return pp::foundation::Status::success();
case DocumentExportMenuAction::show_license_disabled:
services.show_license_disabled();
return pp::foundation::Status::success();
case DocumentExportMenuAction::unavailable_no_canvas:
return pp::foundation::Status::success();
}
return pp::foundation::Status::invalid_argument("unknown document export menu action");
}
}