mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-30 13:26:54 -06:00
M6: add bounded offline Penpot import and export
This commit is contained in:
@@ -11,7 +11,13 @@ use serde_json::{Value, json};
|
||||
use crate::{CliError, CliResult};
|
||||
|
||||
pub fn load(path: &str) -> CliResult<Document> {
|
||||
load_noted(path).map(|(d, _)| d)
|
||||
let (doc, notes) = load_noted(path)?;
|
||||
if ext(path) == "penpot" {
|
||||
for note in notes {
|
||||
eprintln!("note: {note}");
|
||||
}
|
||||
}
|
||||
Ok(doc)
|
||||
}
|
||||
|
||||
/// The document with the loader's notes (repairs of a damaged file, or import warnings).
|
||||
@@ -120,7 +126,8 @@ pub fn write_doc(doc: &Document, page: NodeId, out: &str) -> CliResult<()> {
|
||||
"png" | "jpg" | "jpeg" | "webp" => encode(doc, &render_page(doc, page, 1.0)?, &e)?,
|
||||
"pdf" => page_pdf(doc, page, &PrintSettings::default())?,
|
||||
"fig" => fig_bytes(doc)?,
|
||||
other => return Err(CliError::Usage(format!("unknown output format `.{other}` (figcraft, json, svg, png, jpg, webp, pdf, fig)"))),
|
||||
"penpot" => penpot_bytes(doc)?,
|
||||
other => return Err(CliError::Usage(format!("unknown output format `.{other}` (figcraft, json, svg, png, jpg, webp, pdf, fig, penpot)"))),
|
||||
};
|
||||
write(out, &bytes)
|
||||
}
|
||||
@@ -134,6 +141,14 @@ fn fig_bytes(doc: &Document) -> CliResult<Vec<u8>> {
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn penpot_bytes(doc: &Document) -> CliResult<Vec<u8>> {
|
||||
let (bytes, warnings) = figcraft_engine::cmd::file::penpot_bytes(doc).map_err(|e| CliError::Failed(e.to_string()))?;
|
||||
for w in warnings {
|
||||
eprintln!("note: {w}");
|
||||
}
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// A page as PDF: its top-level frames one page each, or (without frames) everything on one page.
|
||||
fn page_pdf(doc: &Document, page: NodeId, print: &PrintSettings) -> CliResult<Vec<u8>> {
|
||||
let r = if doc.top_frames(page).iter().any(|f| doc.node(*f).is_some_and(|n| n.visible)) {
|
||||
@@ -235,12 +250,16 @@ pub fn export(file: &str, out: &str, t: &Target) -> CliResult<ExitCode> {
|
||||
println!("{out}");
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
if t.format.as_deref().map_or(ext(out) == "penpot", |f| f.eq_ignore_ascii_case("penpot")) {
|
||||
write(out, &penpot_bytes(&doc)?)?;
|
||||
println!("{out}");
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
let page = page_named(&doc, t.page.as_deref())?;
|
||||
let (scale, node, print) = (t.scale, t.node.as_deref(), &t.print);
|
||||
let fmt = match t.format.as_deref() {
|
||||
Some(f) => {
|
||||
image_format(&f.to_ascii_lowercase()).ok_or_else(|| CliError::Usage(format!("unknown --format `{f}` (png|jpg|svg|webp|pdf|fig)")))?
|
||||
}
|
||||
Some(f) => image_format(&f.to_ascii_lowercase())
|
||||
.ok_or_else(|| CliError::Usage(format!("unknown --format `{f}` (png|jpg|svg|webp|pdf|fig|penpot)")))?,
|
||||
None => image_format(&ext(out)).unwrap_or(ExportFormat::Png),
|
||||
};
|
||||
let target = node.map(|q| find_node(&doc, page, q)).transpose()?;
|
||||
|
||||
@@ -17,7 +17,7 @@ pub const USAGE: &str = "figcraft-cli — FigCraft without a window
|
||||
|
||||
USAGE:
|
||||
figcraft-cli info <file> [--json]
|
||||
figcraft-cli export <file> -o <out> [--format png|jpg|svg|webp|pdf|fig] [--scale N] [--page NAME] [--node ID|NAME]
|
||||
figcraft-cli export <file> -o <out> [--format png|jpg|svg|webp|pdf|fig|penpot] [--scale N] [--page NAME] [--node ID|NAME]
|
||||
[--bleed LENGTH] [--crop-marks] [--bleed-marks] [--cmyk]
|
||||
figcraft-cli convert <in> <out>
|
||||
figcraft-cli render <file> -o <out.png> [--scale N] [--page NAME]
|
||||
@@ -33,11 +33,12 @@ Every subcommand takes --no-system-fonts (or set FIGCRAFT_NO_SYSTEM_FONTS=1): te
|
||||
fonts built into FigCraft, for renders that match on every machine. Otherwise installed fonts are
|
||||
indexed the first time text is laid out, as in the app.
|
||||
|
||||
Files: .figcraft (native), .json (FigCraft or REST API file JSON), .fig (best effort; `info` lists what
|
||||
Files: .figcraft (native), .penpot (Penpot 2.x), .json (FigCraft or REST API file JSON), .fig (best effort; `info` lists what
|
||||
wasn't imported), .svg, .png/.jpg/.webp/.gif/.bmp (open as an image layer). `export` writes one file
|
||||
per top-level frame into the <out> directory, or one file when <out> has an image extension or --node
|
||||
is given (`fig`, or an <out> ending in .fig: the whole file as .fig, experimental; other apps may
|
||||
not open every file); PDFs take print options (a bleed like 3mm or 0.125in,
|
||||
not open every file; `penpot`, or an <out> ending in .penpot: the whole file as a Penpot 2.x
|
||||
archive to import into a Penpot team); PDFs take print options (a bleed like 3mm or 0.125in,
|
||||
crop and bleed marks, CMYK colour). `run` executes a JSON array (or JSON lines) of
|
||||
{\"command\": id, \"params\": {...}} in a headless session and prints one JSON result line per step.
|
||||
`plugin publish` sends a plugin folder (figcraft-plugin.json, its module, an optional assets/ folder)
|
||||
|
||||
Reference in New Issue
Block a user