Route live save snapshots through PPI policy

This commit is contained in:
2026-06-06 11:43:50 +02:00
parent 772dc7332b
commit 9d9b93abb1
8 changed files with 148 additions and 23 deletions

View File

@@ -283,10 +283,11 @@ powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.p
state toward `pp_document::CanvasDocument`, including dimensions, active state toward `pp_document::CanvasDocument`, including dimensions, active
layer/frame, layer visibility/opacity/alpha/blend metadata, frame durations, layer/frame, layer visibility/opacity/alpha/blend metadata, frame durations,
captured RGBA8 face payloads, and remaining renderer payload-readback counts, captured RGBA8 face payloads, and remaining renderer payload-readback counts,
plus the save-readiness report now consumed before retained live saves. For plus the save-readiness and save-writer route reports now consumed before
payload-complete or metadata-only snapshots, the same app-core boundary now retained live saves. For payload-complete or metadata-only snapshots, the
exports through the pure `pp_document` PPI writer and reports generated byte same app-core boundary now exports through the pure `pp_document` PPI writer
counts plus decoded dirty-face counts in `ppiExport` JSON. Payload-complete and reports generated byte counts plus decoded dirty-face counts in
`ppiExport` JSON. Payload-complete
snapshots also feed the active frame through the shared `pp_paint_renderer` snapshots also feed the active frame through the shared `pp_paint_renderer`
export-readiness report, reporting texture, transition, command, byte, and export-readiness report, reporting texture, transition, command, byte, and
active-frame payload counts in `rendererUpload` JSON plus `facePngExport` active-frame payload counts in `rendererUpload` JSON plus `facePngExport`
@@ -1172,8 +1173,10 @@ powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.p
route through this bridge before reaching legacy project-save execution, route through this bridge before reaching legacy project-save execution,
overwrite prompts, document field updates, title updates, and keyboard/dialog overwrite prompts, document field updates, title updates, and keyboard/dialog
cleanup. Existing Save, Save As, Save Version, and save-before-workflow cleanup. Existing Save, Save As, Save Version, and save-before-workflow
prepare and log a payload-bearing canvas document snapshot report before prepare and log a payload-bearing canvas document snapshot report, run the
delegating to retained `Canvas::project_save`. Retained legacy UI/canvas app-core pure PPI save-writer route for payload-complete snapshots, and log
generated byte counts before delegating to retained `Canvas::project_save`.
Retained legacy UI/canvas
execution and actual live save serialization remain tracked by `DEBT-0040`, execution and actual live save serialization remain tracked by `DEBT-0040`,
`DEBT-0041`, and `DEBT-0042`; the pure snapshot-to-PPI export handoff is `DEBT-0041`, and `DEBT-0042`; the pure snapshot-to-PPI export handoff is
already validated in `pp_app_core_document_canvas_tests` and already validated in `pp_app_core_document_canvas_tests` and

View File

@@ -524,8 +524,16 @@ agent or engineer to remove them without reconstructing context from chat.
Save, Save As, Save Version, and save-before-workflow now prepare a Save, Save As, Save Version, and save-before-workflow now prepare a
payload-bearing canvas document snapshot plus `pp_app_core` save-readiness payload-bearing canvas document snapshot plus `pp_app_core` save-readiness
report before delegating to retained `Canvas::project_save`. The retained report before delegating to retained `Canvas::project_save`. The retained
writer still owns PPI serialization, progress/threading, and compatibility writer still owned PPI serialization, progress/threading, and compatibility
quirks, so pure writer replacement remains open. quirks, so pure writer replacement remained open.
- 2026-06-06: DEBT-0010/DEBT-0013/DEBT-0040/DEBT-0042 were narrowed again.
`pp_app_core` now owns a tested save-writer route policy for canvas snapshots,
`pano_cli plan-canvas-document-snapshot` emits that route as JSON, and live
Save, Save As, Save Version, and save-before-workflow run the pure PPI
exporter for payload-complete snapshots and log byte counts before retained
`Canvas::project_save` continues. The retained writer still owns actual save
serialization, app metadata mutation, progress/threading, and compatibility
quirks.
- 2026-06-05: DEBT-0010/DEBT-0013 were narrowed again. `pp_app_core` now - 2026-06-05: DEBT-0010/DEBT-0013 were narrowed again. `pp_app_core` now
exports payload-complete or metadata-only canvas document snapshots through exports payload-complete or metadata-only canvas document snapshots through
the pure `pp_document` PPI writer and rejects snapshots that still require the pure `pp_document` PPI writer and rejects snapshots that still require

View File

@@ -699,11 +699,13 @@ now builds the same metadata snapshot from live `Canvas` state and has an
opt-in dirty-face payload snapshot path backed by retained `Layer::snapshot()` opt-in dirty-face payload snapshot path backed by retained `Layer::snapshot()`
readback. Live Save, Save As, Save Version, and save-before-workflow paths now readback. Live Save, Save As, Save Version, and save-before-workflow paths now
prepare and log a payload-completeness report from that snapshot before prepare and log a payload-completeness report from that snapshot before
delegating to retained `Canvas::project_save`; the app-core snapshot boundary delegating to retained `Canvas::project_save`; payload-complete live save
also has a tested pure PPI export helper, and snapshots also run the tested pure PPI exporter and log the app-core
save-writer route/byte count before retained save continues. The app-core
snapshot boundary also has a tested pure PPI export helper, and
`pano_cli plan-canvas-document-snapshot` runs that helper for payload-complete `pano_cli plan-canvas-document-snapshot` runs that helper for payload-complete
snapshots and reports generated byte/dirty-face summaries. The same automation snapshots and reports generated byte/dirty-face summaries plus the same
now feeds payload-complete snapshots through the shared save-writer route JSON. The same automation now feeds payload-complete snapshots through the shared
`pp_paint_renderer::prepare_document_frame_export_readiness` report, which `pp_paint_renderer::prepare_document_frame_export_readiness` report, which
records renderer-neutral six-face texture upload commands and encodes the records renderer-neutral six-face texture upload commands and encodes the
active document frame's six composited faces to PNG bytes. This gives CLI active document frame's six composited faces to PNG bytes. This gives CLI
@@ -2292,13 +2294,16 @@ Results:
automation. automation.
- Live Save, Save As, Save Version, and save-before-workflow execution now - Live Save, Save As, Save Version, and save-before-workflow execution now
prepare a payload-bearing canvas document snapshot and save-readiness report prepare a payload-bearing canvas document snapshot and save-readiness report
through `src/legacy_document_session_services.*` before delegating to the through `src/legacy_document_session_services.*`; payload-complete snapshots
retained `Canvas::project_save` writer, keeping behavior stable while moving now run the pure PPI exporter and log the app-core save-writer route/byte
the app path onto the document/canvas boundary. count before delegating to the retained `Canvas::project_save` writer,
keeping behavior stable while moving the app path onto the document/canvas
boundary.
- `pano_cli plan-canvas-document-snapshot` now emits the same save-readiness - `pano_cli plan-canvas-document-snapshot` now emits the same save-readiness
report (`payloadComplete` and `canExportPpi`) used by the live save bridge, report (`payloadComplete` and `canExportPpi`) used by the live save bridge,
and payload-complete snapshots now run the pure `pp_document` PPI exporter the same save-writer route, and payload-complete snapshots now run the pure
and decoded-project summary before emitting `ppiExport` JSON. `pp_document` PPI exporter and decoded-project summary before emitting
`ppiExport` JSON.
- The same payload-complete snapshot automation now uploads the active document - The same payload-complete snapshot automation now uploads the active document
frame through `pp_paint_renderer::upload_document_frame_faces` and the frame through `pp_paint_renderer::upload_document_frame_faces` and the
`RecordingRenderDevice`, emitting `rendererUpload` JSON with texture, `RecordingRenderDevice`, emitting `rendererUpload` JSON with texture,

View File

@@ -78,6 +78,19 @@ struct DocumentCanvasSaveSnapshotReport {
bool can_export_ppi = false; bool can_export_ppi = false;
}; };
enum class DocumentCanvasSaveWriterAction {
use_document_ppi_writer,
use_legacy_project_save,
};
struct DocumentCanvasSaveWriterRoutePlan {
DocumentCanvasSaveWriterAction action = DocumentCanvasSaveWriterAction::use_legacy_project_save;
bool payload_complete = false;
bool can_export_ppi = false;
bool uses_document_ppi_writer = false;
std::string_view fallback_reason;
};
struct DocumentCanvasPpiExportResult { struct DocumentCanvasPpiExportResult {
DocumentCanvasSaveSnapshotReport report; DocumentCanvasSaveSnapshotReport report;
std::vector<std::byte> bytes; std::vector<std::byte> bytes;
@@ -248,14 +261,31 @@ public:
}; };
} }
[[nodiscard]] constexpr DocumentCanvasSaveWriterRoutePlan plan_document_canvas_save_writer_route(
DocumentCanvasSaveSnapshotReport report) noexcept
{
DocumentCanvasSaveWriterRoutePlan plan;
plan.payload_complete = report.payload_complete;
plan.can_export_ppi = report.can_export_ppi;
if (!report.payload_complete || !report.can_export_ppi) {
plan.fallback_reason = "canvas document snapshot still requires renderer payload readback";
return plan;
}
plan.action = DocumentCanvasSaveWriterAction::use_document_ppi_writer;
plan.uses_document_ppi_writer = true;
return plan;
}
[[nodiscard]] inline pp::foundation::Result<DocumentCanvasPpiExportResult> [[nodiscard]] inline pp::foundation::Result<DocumentCanvasPpiExportResult>
export_document_canvas_save_snapshot_to_ppi(const DocumentCanvasSnapshotResult& snapshot) export_document_canvas_save_snapshot_to_ppi(const DocumentCanvasSnapshotResult& snapshot)
{ {
const auto report = make_document_canvas_save_snapshot_report(snapshot); const auto report = make_document_canvas_save_snapshot_report(snapshot);
if (!report.can_export_ppi) { const auto route = plan_document_canvas_save_writer_route(report);
if (!route.uses_document_ppi_writer) {
return pp::foundation::Result<DocumentCanvasPpiExportResult>::failure( return pp::foundation::Result<DocumentCanvasPpiExportResult>::failure(
pp::foundation::Status::invalid_argument( pp::foundation::Status::invalid_argument(route.fallback_reason.data()));
"canvas document snapshot still requires renderer payload readback"));
} }
auto bytes = pp::document::export_ppi_project_document(snapshot.document); auto bytes = pp::document::export_ppi_project_document(snapshot.document);

View File

@@ -40,6 +40,32 @@ pp::foundation::Status prepare_legacy_document_save_snapshot(App& app, const cha
const auto report = pp::app::make_document_canvas_save_snapshot_report(snapshot.value()); const auto report = pp::app::make_document_canvas_save_snapshot_report(snapshot.value());
log_legacy_document_save_snapshot(context, report); log_legacy_document_save_snapshot(context, report);
const auto route = pp::app::plan_document_canvas_save_writer_route(report);
if (!route.uses_document_ppi_writer) {
LOG(
"%s document save writer retained legacy save: %.*s",
context,
static_cast<int>(route.fallback_reason.size()),
route.fallback_reason.data());
return pp::foundation::Status::success();
}
const auto exported = pp::app::export_document_canvas_save_snapshot_to_ppi(snapshot.value());
if (!exported) {
LOG(
"%s document save writer retained legacy save after PPI export failure: %s",
context,
exported.status().message);
return exported.status();
}
LOG(
"%s document save writer route: document-ppi payloadComplete=%s bytes=%llu capturedFaces=%zu pendingFaces=%zu",
context,
exported.value().report.payload_complete ? "true" : "false",
static_cast<unsigned long long>(exported.value().bytes.size()),
exported.value().report.captured_face_payloads,
exported.value().report.pending_face_payloads);
return pp::foundation::Status::success(); return pp::foundation::Status::success();
} }

View File

@@ -1530,13 +1530,13 @@ if(TARGET pano_cli)
COMMAND pano_cli plan-canvas-document-snapshot --width 128 --height 64 --layers 3 --frames 2 --current-layer 2 --current-frame 1 --hidden-layer 0 --alpha-locked-layer 2 --opacity 0.5 --blend-mode 4 --pending-face-payloads-per-layer 6) COMMAND pano_cli plan-canvas-document-snapshot --width 128 --height 64 --layers 3 --frames 2 --current-layer 2 --current-frame 1 --hidden-layer 0 --alpha-locked-layer 2 --opacity 0.5 --blend-mode 4 --pending-face-payloads-per-layer 6)
set_tests_properties(pano_cli_plan_canvas_document_snapshot_smoke PROPERTIES set_tests_properties(pano_cli_plan_canvas_document_snapshot_smoke PROPERTIES
LABELS "app;document;integration;desktop-fast" LABELS "app;document;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-document-snapshot\".*\"width\":128.*\"height\":64.*\"layers\":3.*\"frames\":2.*\"activeLayer\":2.*\"activeFrame\":1.*\"activeLayerName\":\"Layer 3\".*\"activeLayerOpacity\":0.5.*\"activeLayerBlend\":\"overlay\".*\"activeLayerAlphaLocked\":true.*\"pendingFacePayloads\":18.*\"metadataOnly\":true.*\"requiresRendererPayloadReadback\":true.*\"documentFacePayloads\":0.*\"saveReport\":\\{\"payloadComplete\":false,\"canExportPpi\":false\\}.*\"ppiExport\":\\{\"ready\":false,\"bytes\":0,\"dirtyFaces\":0\\}.*\"rendererUpload\":\\{\"ready\":false,\"textures\":0,\"bytes\":0,\"transitions\":0,\"facePayloads\":0,\"compositedLayerFaces\":0,\"commands\":0,\"uploadCommands\":0,\"transitionCommands\":0\\}.*\"facePngExport\":\\{\"ready\":false,\"faces\":0,\"bytes\":0,\"facePayloads\":0\\}.*\"depthExport\":\\{\"ready\":false,\"width\":0,\"height\":0,\"mergedFaceDraws\":0,\"layerDepthDraws\":0,\"visitedLayers\":0,\"visibleLayers\":0,\"facePayloads\":0,\"requiresRendererReadback\":true\\}") PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-document-snapshot\".*\"width\":128.*\"height\":64.*\"layers\":3.*\"frames\":2.*\"activeLayer\":2.*\"activeFrame\":1.*\"activeLayerName\":\"Layer 3\".*\"activeLayerOpacity\":0.5.*\"activeLayerBlend\":\"overlay\".*\"activeLayerAlphaLocked\":true.*\"pendingFacePayloads\":18.*\"metadataOnly\":true.*\"requiresRendererPayloadReadback\":true.*\"documentFacePayloads\":0.*\"saveReport\":\\{\"payloadComplete\":false,\"canExportPpi\":false\\}.*\"saveWriterRoute\":\\{\"action\":\"use-legacy-project-save\",\"usesDocumentPpiWriter\":false,\"fallbackReason\":\"canvas document snapshot still requires renderer payload readback\"\\}.*\"ppiExport\":\\{\"ready\":false,\"bytes\":0,\"dirtyFaces\":0\\}.*\"rendererUpload\":\\{\"ready\":false,\"textures\":0,\"bytes\":0,\"transitions\":0,\"facePayloads\":0,\"compositedLayerFaces\":0,\"commands\":0,\"uploadCommands\":0,\"transitionCommands\":0\\}.*\"facePngExport\":\\{\"ready\":false,\"faces\":0,\"bytes\":0,\"facePayloads\":0\\}.*\"depthExport\":\\{\"ready\":false,\"width\":0,\"height\":0,\"mergedFaceDraws\":0,\"layerDepthDraws\":0,\"visitedLayers\":0,\"visibleLayers\":0,\"facePayloads\":0,\"requiresRendererReadback\":true\\}")
add_test(NAME pano_cli_plan_canvas_document_snapshot_payload_smoke add_test(NAME pano_cli_plan_canvas_document_snapshot_payload_smoke
COMMAND pano_cli plan-canvas-document-snapshot --width 128 --height 64 --layers 2 --frames 2 --current-layer 1 --current-frame 1 --pending-face-payloads-per-layer 2 --captured-face-payloads-per-layer 2) COMMAND pano_cli plan-canvas-document-snapshot --width 128 --height 64 --layers 2 --frames 2 --current-layer 1 --current-frame 1 --pending-face-payloads-per-layer 2 --captured-face-payloads-per-layer 2)
set_tests_properties(pano_cli_plan_canvas_document_snapshot_payload_smoke PROPERTIES set_tests_properties(pano_cli_plan_canvas_document_snapshot_payload_smoke PROPERTIES
LABELS "app;document;integration;desktop-fast" LABELS "app;document;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-document-snapshot\".*\"layers\":2.*\"frames\":2.*\"activeLayer\":1.*\"activeFrame\":1.*\"pendingFacePayloads\":4.*\"capturedFacePayloads\":4.*\"metadataOnly\":false.*\"requiresRendererPayloadReadback\":false.*\"documentFacePayloads\":4.*\"saveReport\":\\{\"payloadComplete\":true,\"canExportPpi\":true\\}.*\"ppiExport\":\\{\"ready\":true,\"bytes\":[1-9][0-9]*,\"dirtyFaces\":4\\}.*\"rendererUpload\":\\{\"ready\":true,\"textures\":6,\"bytes\":[1-9][0-9]*,\"transitions\":6,\"facePayloads\":2,\"compositedLayerFaces\":2,\"commands\":12,\"uploadCommands\":6,\"transitionCommands\":6\\}.*\"facePngExport\":\\{\"ready\":true,\"faces\":6,\"bytes\":[1-9][0-9]*,\"facePayloads\":2\\}.*\"depthExport\":\\{\"ready\":true,\"width\":1024,\"height\":1024,\"mergedFaceDraws\":6,\"layerDepthDraws\":2,\"visitedLayers\":2,\"visibleLayers\":1,\"facePayloads\":2,\"requiresRendererReadback\":true\\}") PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-document-snapshot\".*\"layers\":2.*\"frames\":2.*\"activeLayer\":1.*\"activeFrame\":1.*\"pendingFacePayloads\":4.*\"capturedFacePayloads\":4.*\"metadataOnly\":false.*\"requiresRendererPayloadReadback\":false.*\"documentFacePayloads\":4.*\"saveReport\":\\{\"payloadComplete\":true,\"canExportPpi\":true\\}.*\"saveWriterRoute\":\\{\"action\":\"use-document-ppi-writer\",\"usesDocumentPpiWriter\":true,\"fallbackReason\":\"\"\\}.*\"ppiExport\":\\{\"ready\":true,\"bytes\":[1-9][0-9]*,\"dirtyFaces\":4\\}.*\"rendererUpload\":\\{\"ready\":true,\"textures\":6,\"bytes\":[1-9][0-9]*,\"transitions\":6,\"facePayloads\":2,\"compositedLayerFaces\":2,\"commands\":12,\"uploadCommands\":6,\"transitionCommands\":6\\}.*\"facePngExport\":\\{\"ready\":true,\"faces\":6,\"bytes\":[1-9][0-9]*,\"facePayloads\":2\\}.*\"depthExport\":\\{\"ready\":true,\"width\":1024,\"height\":1024,\"mergedFaceDraws\":6,\"layerDepthDraws\":2,\"visitedLayers\":2,\"visibleLayers\":1,\"facePayloads\":2,\"requiresRendererReadback\":true\\}")
add_test(NAME pano_cli_plan_canvas_document_snapshot_no_canvas add_test(NAME pano_cli_plan_canvas_document_snapshot_no_canvas
COMMAND pano_cli plan-canvas-document-snapshot --no-canvas) COMMAND pano_cli plan-canvas-document-snapshot --no-canvas)

View File

@@ -208,6 +208,38 @@ void snapshot_plan_attaches_captured_face_payloads(pp::tests::Harness& harness)
} }
} }
void save_writer_route_uses_ppi_writer_for_complete_payloads(pp::tests::Harness& harness)
{
pp::app::DocumentCanvasSaveSnapshotReport report;
report.payload_complete = true;
report.can_export_ppi = true;
const auto plan = pp::app::plan_document_canvas_save_writer_route(report);
PP_EXPECT(harness, plan.action == pp::app::DocumentCanvasSaveWriterAction::use_document_ppi_writer);
PP_EXPECT(harness, plan.uses_document_ppi_writer);
PP_EXPECT(harness, plan.payload_complete);
PP_EXPECT(harness, plan.can_export_ppi);
PP_EXPECT(harness, plan.fallback_reason.empty());
}
void save_writer_route_falls_back_for_pending_payloads(pp::tests::Harness& harness)
{
pp::app::DocumentCanvasSaveSnapshotReport report;
report.payload_complete = false;
report.can_export_ppi = false;
const auto plan = pp::app::plan_document_canvas_save_writer_route(report);
PP_EXPECT(harness, plan.action == pp::app::DocumentCanvasSaveWriterAction::use_legacy_project_save);
PP_EXPECT(harness, !plan.uses_document_ppi_writer);
PP_EXPECT(harness, !plan.payload_complete);
PP_EXPECT(harness, !plan.can_export_ppi);
PP_EXPECT(
harness,
plan.fallback_reason == "canvas document snapshot still requires renderer payload readback");
}
void snapshot_plan_rejects_invalid_canvas_state(pp::tests::Harness& harness) void snapshot_plan_rejects_invalid_canvas_state(pp::tests::Harness& harness)
{ {
const std::uint32_t frames[] { 100U }; const std::uint32_t frames[] { 100U };
@@ -387,6 +419,8 @@ int main()
harness.run("snapshot plan projects canvas metadata", snapshot_plan_projects_canvas_metadata); harness.run("snapshot plan projects canvas metadata", snapshot_plan_projects_canvas_metadata);
harness.run("snapshot plan defaults empty names and frames", snapshot_plan_defaults_empty_names_and_frames); harness.run("snapshot plan defaults empty names and frames", snapshot_plan_defaults_empty_names_and_frames);
harness.run("snapshot plan attaches captured face payloads", snapshot_plan_attaches_captured_face_payloads); harness.run("snapshot plan attaches captured face payloads", snapshot_plan_attaches_captured_face_payloads);
harness.run("save writer route uses ppi writer for complete payloads", save_writer_route_uses_ppi_writer_for_complete_payloads);
harness.run("save writer route falls back for pending payloads", save_writer_route_falls_back_for_pending_payloads);
harness.run("snapshot plan rejects invalid canvas state", snapshot_plan_rejects_invalid_canvas_state); harness.run("snapshot plan rejects invalid canvas state", snapshot_plan_rejects_invalid_canvas_state);
harness.run("clear plan records legacy canvas effects", clear_plan_records_legacy_canvas_effects); harness.run("clear plan records legacy canvas effects", clear_plan_records_legacy_canvas_effects);
harness.run("clear plan noops without canvas", clear_plan_noops_without_canvas); harness.run("clear plan noops without canvas", clear_plan_noops_without_canvas);

View File

@@ -949,6 +949,18 @@ const char* document_save_decision_name(pp::app::DocumentSaveDecision decision)
return "no-op"; return "no-op";
} }
const char* document_canvas_save_writer_action_name(pp::app::DocumentCanvasSaveWriterAction action) noexcept
{
switch (action) {
case pp::app::DocumentCanvasSaveWriterAction::use_document_ppi_writer:
return "use-document-ppi-writer";
case pp::app::DocumentCanvasSaveWriterAction::use_legacy_project_save:
return "use-legacy-project-save";
}
return "use-legacy-project-save";
}
const char* document_workflow_decision_name(pp::app::DocumentWorkflowDecision decision) noexcept const char* document_workflow_decision_name(pp::app::DocumentWorkflowDecision decision) noexcept
{ {
switch (decision) { switch (decision) {
@@ -6180,6 +6192,7 @@ int plan_canvas_document_snapshot(int argc, char** argv)
const auto& document = value.document; const auto& document = value.document;
const auto& active_layer = document.layers()[document.active_layer_index()]; const auto& active_layer = document.layers()[document.active_layer_index()];
const auto save_report = pp::app::make_document_canvas_save_snapshot_report(value); const auto save_report = pp::app::make_document_canvas_save_snapshot_report(value);
const auto save_writer_route = pp::app::plan_document_canvas_save_writer_route(save_report);
bool ppi_export_ready = false; bool ppi_export_ready = false;
std::size_t ppi_export_bytes = 0; std::size_t ppi_export_bytes = 0;
std::uint32_t ppi_export_dirty_faces = 0; std::uint32_t ppi_export_dirty_faces = 0;
@@ -6205,7 +6218,7 @@ int plan_canvas_document_snapshot(int argc, char** argv)
std::size_t depth_export_visible_layers = 0; std::size_t depth_export_visible_layers = 0;
std::size_t depth_export_face_payloads = 0; std::size_t depth_export_face_payloads = 0;
bool depth_export_requires_renderer_readback = value.requires_renderer_payload_readback; bool depth_export_requires_renderer_readback = value.requires_renderer_payload_readback;
if (save_report.can_export_ppi) { if (save_writer_route.uses_document_ppi_writer) {
const auto exported = pp::app::export_document_canvas_save_snapshot_to_ppi(value); const auto exported = pp::app::export_document_canvas_save_snapshot_to_ppi(value);
if (!exported) { if (!exported) {
print_error("plan-canvas-document-snapshot", exported.status().message); print_error("plan-canvas-document-snapshot", exported.status().message);
@@ -6296,6 +6309,12 @@ int plan_canvas_document_snapshot(int argc, char** argv)
<< ",\"documentFacePayloads\":" << document.face_pixel_payload_count() << ",\"documentFacePayloads\":" << document.face_pixel_payload_count()
<< ",\"saveReport\":{\"payloadComplete\":" << json_bool(save_report.payload_complete) << ",\"saveReport\":{\"payloadComplete\":" << json_bool(save_report.payload_complete)
<< ",\"canExportPpi\":" << json_bool(save_report.can_export_ppi) << ",\"canExportPpi\":" << json_bool(save_report.can_export_ppi)
<< "},\"saveWriterRoute\":{\"action\":\""
<< document_canvas_save_writer_action_name(save_writer_route.action)
<< "\",\"usesDocumentPpiWriter\":" << json_bool(save_writer_route.uses_document_ppi_writer)
<< ",\"fallbackReason\":\""
<< json_escape(std::string(save_writer_route.fallback_reason))
<< "\""
<< "},\"ppiExport\":{\"ready\":" << json_bool(ppi_export_ready) << "},\"ppiExport\":{\"ready\":" << json_bool(ppi_export_ready)
<< ",\"bytes\":" << ppi_export_bytes << ",\"bytes\":" << ppi_export_bytes
<< ",\"dirtyFaces\":" << ppi_export_dirty_faces << ",\"dirtyFaces\":" << ppi_export_dirty_faces