mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Export: a progress dialog with Cancel for background exports
While an export runs in the background (Background Export), a dialog shows what is being exported and how far it has got: artboard by artboard for a raster export of several artboards, busy otherwise. Cancel (also Esc) stops it and writes no file; Continue in Background leaves it to the status bar. It opens after 0.4 s and never over another dialog. Built on the shared vectorcraft_doc::cancel (#2546): fileio::progress::Progress wraps its CancelToken and a progress sink, so a cancelled export stops drawing and keeps no half-drawn pattern tile or effect raster. - ui: background jobs carry their Progress; export_progress draws the dialog; UI commands file.cancelExport and file.exportInBackground. - i18n: the strings in every language with complete menus, Danish, Dutch and Indonesian included; "{n} more exports waiting" is a plural (trn). - docs: ui-parity, gaps (G12) and ROADMAP rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dt49z1ubK9x1bPq51APgz3
This commit is contained in:
committed by
bflatastic
co-authored by
Claude Opus 5.5
parent
a275496ef4
commit
ec410552cf
+3
-1
File diff suppressed because one or more lines are too long
@@ -40,6 +40,7 @@ pub mod pdf;
|
||||
mod pdfimport;
|
||||
pub mod pngtext;
|
||||
pub mod ppi;
|
||||
pub mod progress;
|
||||
pub mod psdread;
|
||||
mod save;
|
||||
mod screens;
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
//! Progress and Cancel of an export running off the UI thread (Background Export), on the shared
|
||||
//! [`vectorcraft_doc::cancel`] mechanism.
|
||||
//!
|
||||
//! The frontend makes a [`Progress`], runs the export inside [`scope`] on its worker and reads the
|
||||
//! counts from the UI thread to draw them; its Cancel button calls [`Progress::cancel`]. Inside the
|
||||
//! scope the encoders that work in steps (raster formats: one artboard at a time) report each step
|
||||
//! through [`vectorcraft_doc::cancel::progress`] and stop at the next check once cancelled; the
|
||||
//! renderer stops drawing and keeps no half-drawn pattern tile or effect raster. Encoders that
|
||||
//! make one file in one go report nothing: the frontend shows them as busy and checks
|
||||
//! [`Progress::cancelled`] itself before writing, so a cancelled export never writes a file.
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
use vectorcraft_doc::cancel::{self, CancelToken, ProgressSink};
|
||||
|
||||
/// How far an export has got, shared between the worker and the UI thread.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Progress {
|
||||
token: CancelToken,
|
||||
done: AtomicU64,
|
||||
total: AtomicU64,
|
||||
}
|
||||
|
||||
impl Progress {
|
||||
pub fn new() -> Arc<Self> {
|
||||
Arc::new(Self::default())
|
||||
}
|
||||
|
||||
/// `(steps done, steps in all)`; `total` is 0 while the export can't say (one file made in one go).
|
||||
pub fn counts(&self) -> (u64, u64) {
|
||||
let total = self.total.load(Ordering::Relaxed);
|
||||
(self.done.load(Ordering::Relaxed).min(total), total)
|
||||
}
|
||||
|
||||
/// The share of the export done, 0 to 1; `None` while the export can't say.
|
||||
pub fn fraction(&self) -> Option<f64> {
|
||||
let (done, total) = self.counts();
|
||||
(total > 0).then(|| done as f64 / total as f64)
|
||||
}
|
||||
|
||||
/// Ask the export to stop: it writes nothing more, and a render under way stops drawing.
|
||||
pub fn cancel(&self) {
|
||||
self.token.cancel();
|
||||
}
|
||||
|
||||
pub fn cancelled(&self) -> bool {
|
||||
self.token.is_cancelled()
|
||||
}
|
||||
}
|
||||
|
||||
/// Run `f` with `progress`'s token in effect on this thread and the steps reported to it.
|
||||
pub fn scope<R>(progress: &Arc<Progress>, f: impl FnOnce() -> R) -> R {
|
||||
let to = Arc::clone(progress);
|
||||
let sink: ProgressSink = Rc::new(move |done, total, _| {
|
||||
to.total.store(total, Ordering::Relaxed);
|
||||
to.done.store(done, Ordering::Relaxed);
|
||||
});
|
||||
cancel::scope(&progress.token, Some(sink), f)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_raster_export_counts_its_artboards_and_stops_once_cancelled() {
|
||||
let mut doc = vectorcraft_doc::Document::new(100.0, 100.0);
|
||||
let second = doc.artboards[0].clone();
|
||||
doc.artboards.push(second);
|
||||
let p = Progress::new();
|
||||
assert_eq!(p.fraction(), None, "nothing reported yet");
|
||||
let png = scope(&p, || super::super::encode_all(&doc, "png", &serde_json::json!({"useArtboards": true})));
|
||||
assert!(png.is_ok(), "{:?}", png.err());
|
||||
assert_eq!(p.counts(), (1, 2), "the step under way when the last artboard started");
|
||||
p.cancel();
|
||||
let r = scope(&p, || super::super::encode_all(&doc, "png", &serde_json::json!({"useArtboards": true})));
|
||||
assert!(matches!(r, Err(crate::EngineError::Cancelled)), "{:?}", r.err());
|
||||
assert!(!cancel::is_cancelled(), "the scope ended");
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,11 @@
|
||||
//! It needs a thread-safe writer from the host ([`crate::Services::write_shared`], desktop);
|
||||
//! without one (the web, which downloads) the work runs at once, as with the preference off.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::{Receiver, Sender, channel};
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_engine::cmd::fileio::progress::Progress;
|
||||
use vectorcraft_engine::file_access::ScopeHandle;
|
||||
|
||||
use crate::VectorcraftApp;
|
||||
@@ -50,9 +52,24 @@ pub struct Job {
|
||||
pub label: String,
|
||||
/// The document the job reads (`DocState::uid`), if any.
|
||||
pub doc: Option<u64>,
|
||||
/// How far an export has got, and its Cancel ([`crate::export_progress`] shows it).
|
||||
pub progress: Option<Arc<Progress>>,
|
||||
/// Continue in Background: the progress dialog no longer shows this job.
|
||||
pub hidden: bool,
|
||||
then: Then,
|
||||
}
|
||||
|
||||
impl Job {
|
||||
pub fn id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Was the job cancelled (it writes nothing more; its result is still on its way)?
|
||||
pub fn cancelled(&self) -> bool {
|
||||
self.progress.as_ref().is_some_and(|p| p.cancelled())
|
||||
}
|
||||
}
|
||||
|
||||
/// The worker and the jobs it has not finished.
|
||||
#[derive(Default)]
|
||||
pub struct Background {
|
||||
@@ -93,6 +110,26 @@ impl Background {
|
||||
pub fn busy_with(&self, uid: u64) -> bool {
|
||||
self.jobs.iter().any(|j| j.doc == Some(uid))
|
||||
}
|
||||
|
||||
/// Cancel every export in progress → how many there were.
|
||||
pub fn cancel_exports(&mut self) -> usize {
|
||||
let mut n = 0;
|
||||
for p in self.jobs.iter().filter(|j| !j.cancelled()).filter_map(|j| j.progress.as_ref()) {
|
||||
p.cancel();
|
||||
n += 1;
|
||||
}
|
||||
n
|
||||
}
|
||||
|
||||
/// Hide the exports in progress from the progress dialog (they go on) → how many there were.
|
||||
pub fn hide_exports(&mut self) -> usize {
|
||||
let mut n = 0;
|
||||
for j in self.jobs.iter_mut().filter(|j| j.progress.is_some() && !j.hidden && !j.cancelled()) {
|
||||
j.hidden = true;
|
||||
n += 1;
|
||||
}
|
||||
n
|
||||
}
|
||||
}
|
||||
|
||||
/// Run `work` on a worker thread when `enabled` and the host can write from one, else now; `then`
|
||||
@@ -106,6 +143,20 @@ pub fn run(
|
||||
doc: Option<u64>,
|
||||
work: impl FnOnce(Writer) -> Result<Value, String> + Send + 'static,
|
||||
then: impl FnOnce(&mut VectorcraftApp, Result<Value, String>) -> Result<Value, String> + 'static,
|
||||
) -> Result<Value, String> {
|
||||
run_with_progress(app, enabled, label, doc, None, work, then)
|
||||
}
|
||||
|
||||
/// [`run`] for work that reports to `progress` (an export): the progress dialog shows it while it
|
||||
/// runs in the background, with Cancel.
|
||||
pub fn run_with_progress(
|
||||
app: &mut VectorcraftApp,
|
||||
enabled: bool,
|
||||
label: String,
|
||||
doc: Option<u64>,
|
||||
progress: Option<Arc<Progress>>,
|
||||
work: impl FnOnce(Writer) -> Result<Value, String> + Send + 'static,
|
||||
then: impl FnOnce(&mut VectorcraftApp, Result<Value, String>) -> Result<Value, String> + 'static,
|
||||
) -> Result<Value, String> {
|
||||
let mut work: Work = Box::new(work);
|
||||
let shared = app.services.write_shared.clone().filter(|_| enabled && app.services.download.is_none());
|
||||
@@ -116,7 +167,7 @@ pub fn run(
|
||||
Ok(()) => {
|
||||
let bg = &mut app.background;
|
||||
bg.next_id += 1;
|
||||
bg.jobs.push(Job { id, label: label.clone(), doc, then: Box::new(then) });
|
||||
bg.jobs.push(Job { id, label: label.clone(), doc, progress, hidden: false, then: Box::new(then) });
|
||||
if label.is_empty() {
|
||||
return Ok(json!({ "background": true }));
|
||||
}
|
||||
|
||||
@@ -739,7 +739,7 @@ pub fn status_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
.background
|
||||
.jobs
|
||||
.iter()
|
||||
.filter(|j| !j.label.is_empty())
|
||||
.filter(|j| !j.label.is_empty() && !j.cancelled())
|
||||
.map(|j| j.label.as_str())
|
||||
.chain(app.opening.pending.iter().map(|p| p.label.as_str()))
|
||||
.collect();
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
//! The export progress dialog: while an export runs in the background (Background Export) a modal
|
||||
//! window shows what is being exported, how far it has got (artboard by artboard for raster
|
||||
//! exports of several artboards; busy for one artboard and for formats made in one go, which
|
||||
//! report no steps) and two buttons: Cancel (`file.cancelExport`, also
|
||||
//! Esc) stops it and writes nothing, Continue in Background (`file.exportInBackground`) closes the
|
||||
//! dialog and leaves the export to the status bar.
|
||||
//!
|
||||
//! It opens after [`DELAY`] so a quick export doesn't flash it, and waits while another dialog is
|
||||
//! open (the overwrite question, an options dialog): never two modals at once.
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use crate::dialogs::modal;
|
||||
use crate::theme::Tokens;
|
||||
use crate::{VectorcraftApp, widgets};
|
||||
|
||||
/// Seconds an export runs before the dialog opens.
|
||||
pub const DELAY: f64 = 0.4;
|
||||
|
||||
/// Where the job a frame first saw is remembered (its id, the time it was first seen).
|
||||
fn seen_id() -> egui::Id {
|
||||
egui::Id::new("export-progress-seen")
|
||||
}
|
||||
|
||||
/// What the dialog shows: (label, steps done, steps in all, share done, exports after this one).
|
||||
fn shown(app: &VectorcraftApp, now: f64, ctx: &egui::Context) -> Option<(String, u64, u64, f32, usize)> {
|
||||
let jobs: Vec<_> = app.background.jobs.iter().filter(|j| !j.hidden && !j.cancelled()).filter(|j| j.progress.is_some()).collect();
|
||||
let first = jobs.first()?;
|
||||
let since = ctx.data_mut(|m| {
|
||||
let seen = m.get_temp_mut_or_insert_with(seen_id(), || (first.id(), now));
|
||||
if seen.0 != first.id() {
|
||||
*seen = (first.id(), now);
|
||||
}
|
||||
seen.1
|
||||
});
|
||||
if now - since < DELAY {
|
||||
return None;
|
||||
}
|
||||
let (done, total) = first.progress.as_ref().map(|p| p.counts()).unwrap_or_default();
|
||||
let frac = first.progress.as_ref().and_then(|p| p.fraction()).unwrap_or(0.0) as f32;
|
||||
Some((first.label.clone(), done, total, frac, jobs.len() - 1))
|
||||
}
|
||||
|
||||
pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
let now = ctx.input(|i| i.time);
|
||||
let Some((label, done, total, frac, more)) = shown(app, now, ctx) else { return };
|
||||
if app.ui.dialog.is_some() {
|
||||
return;
|
||||
}
|
||||
let t = Tokens::get(ctx);
|
||||
let heading = format!("{}…", crate::i18n::msg(&label));
|
||||
let mut action = None;
|
||||
modal::show(ctx, &heading, egui::Id::new("export-progress"), -40.0, 20, |ui| {
|
||||
ui.set_width(360.0);
|
||||
modal::heading(ui, &heading);
|
||||
ui.add_space(12.0);
|
||||
// One step says nothing a spinner doesn't: the bar would sit at 0 % until the file is written.
|
||||
if total > 1 {
|
||||
let step = crate::i18n::fmt(tl!("Artboard {n} of {total}"), &[("n", &(done + 1).min(total).to_string()), ("total", &total.to_string())]);
|
||||
// The count on its own line: drawn over the bar it would straddle the fill's edge.
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(egui::RichText::new(step).color(t.text_dim));
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
ui.label(egui::RichText::new(format!("{:.0} %", frac * 100.0)).color(t.text_dim));
|
||||
});
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
ui.add(egui::ProgressBar::new(frac).desired_width(360.0).desired_height(6.0).fill(t.accent));
|
||||
} else {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(egui::Spinner::new().size(14.0).color(t.accent));
|
||||
ui.label(egui::RichText::new(tl!("Encoding and writing the file…")).color(t.text_dim));
|
||||
});
|
||||
}
|
||||
if more > 0 {
|
||||
ui.add_space(6.0);
|
||||
let queued = crate::i18n::tn(more as u64, "{n} more export waiting", "{n} more exports waiting");
|
||||
ui.label(egui::RichText::new(queued).size(11.5).color(t.text_dim));
|
||||
}
|
||||
ui.add_space(16.0);
|
||||
let row = egui::vec2(ui.min_rect().width(), ui.spacing().interact_size.y);
|
||||
ui.allocate_ui_with_layout(row, egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if widgets::secondary_button(ui, "Cancel").clicked() {
|
||||
action = Some("file.cancelExport");
|
||||
}
|
||||
ui.add_space(8.0);
|
||||
if widgets::secondary_button(ui, "Continue in Background").clicked() {
|
||||
action = Some("file.exportInBackground");
|
||||
}
|
||||
});
|
||||
});
|
||||
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
|
||||
action = Some("file.cancelExport");
|
||||
}
|
||||
if let Some(id) = action
|
||||
&& let Err(e) = app.run(id, json!({}))
|
||||
{
|
||||
app.status(e);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_engine::Session;
|
||||
use vectorcraft_engine::cmd::fileio::progress::Progress;
|
||||
|
||||
use crate::{Services, VectorcraftApp, background};
|
||||
|
||||
/// An app whose background writer waits until `go` is set.
|
||||
fn app(go: Arc<std::sync::atomic::AtomicBool>) -> VectorcraftApp {
|
||||
let services = Services {
|
||||
write_shared: Some(Arc::new(move |_: &str, _: &[u8]| {
|
||||
while !go.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
}
|
||||
Ok(())
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
VectorcraftApp::new(Session::new(), services)
|
||||
}
|
||||
|
||||
/// Draws one frame of the dialog at `time` seconds → the texts it drew.
|
||||
fn frame(app: &mut VectorcraftApp, ctx: &egui::Context, time: f64) -> String {
|
||||
let mut out = ctx.run_ui(egui::RawInput { time: Some(time), ..Default::default() }, |ui| super::show(app, ui.ctx()));
|
||||
out.textures_delta.clear();
|
||||
let mut texts = String::new();
|
||||
for shape in out.shapes {
|
||||
if let egui::epaint::Shape::Text(t) = shape.shape {
|
||||
texts.push_str(t.galley.text());
|
||||
texts.push('\n');
|
||||
}
|
||||
}
|
||||
texts
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opens_after_the_delay_and_cancel_closes_it() {
|
||||
let go = Arc::new(std::sync::atomic::AtomicBool::new(false));
|
||||
let mut app = app(go.clone());
|
||||
let p = Progress::new();
|
||||
let r = background::run_with_progress(
|
||||
&mut app,
|
||||
true,
|
||||
"Exporting a.png".into(),
|
||||
None,
|
||||
Some(p.clone()),
|
||||
|w| w("/a.png", b"x").map(|()| json!({})),
|
||||
|_, r| r,
|
||||
);
|
||||
assert_eq!(r.unwrap()["background"], true);
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
assert!(!frame(&mut app, &ctx, 0.0).contains("Exporting a.png"), "not before the delay");
|
||||
frame(&mut app, &ctx, 1.0); // a new window measures itself unseen first
|
||||
let shown = frame(&mut app, &ctx, 1.1);
|
||||
assert!(shown.contains("Exporting a.png") && shown.contains("Cancel") && shown.contains("Continue in Background"), "{shown}");
|
||||
app.run("file.cancelExport", json!({})).unwrap();
|
||||
assert!(p.cancelled());
|
||||
assert_eq!(app.ui.status, crate::io::EXPORT_CANCELLED);
|
||||
assert!(!frame(&mut app, &ctx, 2.0).contains("Exporting a.png"), "a cancelled export isn't shown");
|
||||
go.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
background::wait_all(&mut app);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn continue_in_background_hides_it_and_the_export_goes_on() {
|
||||
let go = Arc::new(std::sync::atomic::AtomicBool::new(false));
|
||||
let mut app = app(go.clone());
|
||||
let p = Progress::new();
|
||||
background::run_with_progress(
|
||||
&mut app,
|
||||
true,
|
||||
"Exporting b.png".into(),
|
||||
None,
|
||||
Some(p.clone()),
|
||||
|w| w("/b.png", b"x").map(|()| json!({})),
|
||||
|_, r| r,
|
||||
)
|
||||
.unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
frame(&mut app, &ctx, 0.0);
|
||||
frame(&mut app, &ctx, 1.0);
|
||||
assert!(frame(&mut app, &ctx, 1.1).contains("Exporting b.png"));
|
||||
assert_eq!(app.run("file.exportInBackground", json!({})).unwrap()["hidden"], 1);
|
||||
assert!(!frame(&mut app, &ctx, 2.0).contains("Exporting b.png"));
|
||||
assert!(!p.cancelled());
|
||||
go.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
background::wait_all(&mut app);
|
||||
assert!(app.background.jobs.is_empty());
|
||||
}
|
||||
}
|
||||
@@ -5153,6 +5153,12 @@ theme Appearance Външен вид
|
||||
Enable Guides for 9-Slice Scaling Включване на водачи за 9-slice мащабиране
|
||||
Persistent Постоянна
|
||||
Library Panel Options Опции на панела с библиотека
|
||||
Artboard {n} of {total} Работна област {n} от {total}
|
||||
Encoding and writing the file… Кодиране и записване на файла…
|
||||
@plural {n} more export waiting|{n} more exports waiting Още {n} експортиране в изчакване|Още {n} експортирания в изчакване
|
||||
Continue in Background Продължаване във фонов режим
|
||||
Cancel Export Отказ от експортирането
|
||||
@msg Export cancelled Експортирането е отказано
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Желано
|
||||
Word Spacing: Разстояние между думите:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5188,6 +5188,12 @@ mark-style Japanese Japonské
|
||||
@msg too many library panels were opened in this session v této relaci bylo otevřeno příliš mnoho panelů knihoven
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Obraz má {_1} × {_2} pixelů, na jedné straně více než {LARGE_TRACE_SIDE}: vektorizuje se, ale pomaleji. Rychleji se vektorizuje s předvolbou nižší věrnosti nebo menší kopie obrazu.
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Obraz byl vektorizován v rozlišení {_1} × {_2} pixelů, zmenšený z {_3} × {_4} (nejvýše {_5} megapixelů).
|
||||
Artboard {n} of {total} Pracovní plocha {n} z {total}
|
||||
Encoding and writing the file… Kódování a zápis souboru…
|
||||
@plural {n} more export waiting|{n} more exports waiting Čeká ještě {n} export|Čekají ještě {n} exporty|Čeká ještě {n} exportů
|
||||
Continue in Background Pokračovat na pozadí
|
||||
Cancel Export Zrušit export
|
||||
@msg Export cancelled Export byl zrušen
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Požadované
|
||||
Word Spacing: Mezery mezi slovy:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5172,6 +5172,12 @@ swatch Bright Violet Kraftig violet
|
||||
Typing Indtastning
|
||||
Persistent Vedvarende
|
||||
Library Panel Options Indstillinger for bibliotekspanel
|
||||
Artboard {n} of {total} Tegneflade {n} af {total}
|
||||
Encoding and writing the file… Koder og skriver filen…
|
||||
@plural {n} more export waiting|{n} more exports waiting {n} eksport mere venter|{n} eksporter mere venter
|
||||
Continue in Background Fortsæt i baggrunden
|
||||
Cancel Export Annullér eksport
|
||||
@msg Export cancelled Eksport annulleret
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Ønsket
|
||||
Word Spacing: Ordafstand:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4763,6 +4763,12 @@ effect Regular Regelmäßig
|
||||
@msg too many library panels were opened in this session in dieser Sitzung wurden zu viele Bibliotheksbedienfelder geöffnet
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Das Bild hat {_1} × {_2} Pixel, mehr als {LARGE_TRACE_SIDE} auf einer Seite: Es wird nachgezeichnet, aber langsamer. Eine Vorgabe mit geringerer Detailtreue oder eine kleinere Kopie des Bildes geht schneller.
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Das Bild wurde mit {_1} × {_2} Pixeln nachgezeichnet, verkleinert von {_3} × {_4} (höchstens {_5} Megapixel).
|
||||
Artboard {n} of {total} Zeichenfläche {n} von {total}
|
||||
Encoding and writing the file… Datei wird codiert und geschrieben…
|
||||
@plural {n} more export waiting|{n} more exports waiting {n} weiterer Export wartet|{n} weitere Exporte warten
|
||||
Continue in Background Im Hintergrund fortsetzen
|
||||
Cancel Export Export abbrechen
|
||||
@msg Export cancelled Export abgebrochen
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Optimal
|
||||
Word Spacing: Wortabstand:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5140,6 +5140,12 @@ effect Points Σημεία
|
||||
@msg too many library panels were opened in this session άνοιξαν πάρα πολλοί πίνακες βιβλιοθηκών σε αυτή τη συνεδρία
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Η εικόνα είναι {_1} × {_2} εικονοστοιχεία, πάνω από {LARGE_TRACE_SIDE} σε μία πλευρά: ανιχνεύεται, αλλά πιο αργά. Μια προρύθμιση χαμηλότερης πιστότητας ή ένα μικρότερο αντίγραφο της εικόνας ανιχνεύεται γρηγορότερα.
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Η εικόνα ανιχνεύτηκε σε {_1} × {_2} εικονοστοιχεία, σμικρυμένη από {_3} × {_4} (το πολύ {_5} megapixel).
|
||||
Artboard {n} of {total} Πίνακας σχεδίασης {n} από {total}
|
||||
Encoding and writing the file… Κωδικοποίηση και εγγραφή του αρχείου…
|
||||
@plural {n} more export waiting|{n} more exports waiting {n} ακόμη εξαγωγή σε αναμονή|{n} ακόμη εξαγωγές σε αναμονή
|
||||
Continue in Background Συνέχεια στο παρασκήνιο
|
||||
Cancel Export Ακύρωση εξαγωγής
|
||||
@msg Export cancelled Η εξαγωγή ακυρώθηκε
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Επιθυμητό
|
||||
Word Spacing: Απόσταση λέξεων:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2006 and column 19.
|
@@ -5130,6 +5130,12 @@ mark-style Japanese Japonesas
|
||||
@msg too many library panels were opened in this session se abrieron demasiados paneles de biblioteca en esta sesión
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. La imagen tiene {_1} × {_2} píxeles, más de {LARGE_TRACE_SIDE} en un lado: se calcará, pero más despacio. Un ajuste preestablecido de menor fidelidad o una copia más pequeña de la imagen se calcan más rápido.
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). La imagen se calcó a {_1} × {_2} píxeles, reducida desde {_3} × {_4} ({_5} megapíxeles como máximo).
|
||||
Artboard {n} of {total} Mesa de trabajo {n} de {total}
|
||||
Encoding and writing the file… Codificando y escribiendo el archivo…
|
||||
@plural {n} more export waiting|{n} more exports waiting {n} exportación más en espera|{n} exportaciones más en espera
|
||||
Continue in Background Continuar en segundo plano
|
||||
Cancel Export Cancelar exportación
|
||||
@msg Export cancelled Exportación cancelada
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Deseado
|
||||
Word Spacing: Espaciado entre palabras:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5152,6 +5152,12 @@ mark-style Japanese Japanilainen
|
||||
@msg too many library panels were opened in this session tässä istunnossa on avattu liian monta kirjastopaneelia
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Kuva on {_1} × {_2} kuvapistettä, yhdeltä sivultaan yli {LARGE_TRACE_SIDE}: se jäljitetään, mutta hitaammin. Pienemmän tarkkuuden esiasetus tai kuvan pienempi kopio jäljittyy nopeammin.
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Kuva jäljitettiin kokoon {_1} × {_2} kuvapistettä pienennettynä koosta {_3} × {_4} (enintään {_5} megapikseliä).
|
||||
Artboard {n} of {total} Piirtoalusta {n}/{total}
|
||||
Encoding and writing the file… Tiedostoa koodataan ja kirjoitetaan…
|
||||
@plural {n} more export waiting|{n} more exports waiting {n} vienti odottaa|{n} vientiä odottaa
|
||||
Continue in Background Jatka taustalla
|
||||
Cancel Export Peruuta vienti
|
||||
@msg Export cancelled Vienti peruutettiin
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Haluttu
|
||||
Word Spacing: Sanaväli:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5187,6 +5187,12 @@ theme Appearance Apparence
|
||||
@msg too many library panels were opened in this session trop de panneaux de bibliothèque ont été ouverts dans cette session
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. L’image fait {_1} × {_2} pixels, plus de {LARGE_TRACE_SIDE} sur un côté : elle est vectorisée, mais plus lentement. Un paramètre prédéfini moins fidèle ou une copie plus petite de l’image se vectorise plus vite.
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). L’image a été vectorisée à {_1} × {_2} pixels, réduite de {_3} × {_4} ({_5} mégapixels au plus).
|
||||
Artboard {n} of {total} Plan de travail {n} sur {total}
|
||||
Encoding and writing the file… Encodage et écriture du fichier…
|
||||
@plural {n} more export waiting|{n} more exports waiting {n} autre exportation en attente|{n} autres exportations en attente
|
||||
Continue in Background Continuer en arrière-plan
|
||||
Cancel Export Annuler l’exportation
|
||||
@msg Export cancelled Exportation annulée
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Optimal
|
||||
Word Spacing: Espacement des mots :
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5174,6 +5174,12 @@ imageTrace Outline Körvonal
|
||||
@msg too many library panels were opened in this session ebben a munkamenetben túl sok könyvtárpanel nyílt meg
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. A kép {_1} × {_2} képpont, egyik oldalán több mint {LARGE_TRACE_SIDE}: a vektorizálás megtörténik, de lassabban. Kisebb hűségű előbeállítással vagy a kép kisebb másolatával gyorsabb.
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). A kép vektorizálása {_1} × {_2} képponton történt, {_3} × {_4} méretről kicsinyítve (legfeljebb {_5} megapixel).
|
||||
Artboard {n} of {total} Rajztábla: {n} / {total}
|
||||
Encoding and writing the file… A fájl kódolása és írása…
|
||||
@plural {n} more export waiting|{n} more exports waiting Még {n} exportálás várakozik
|
||||
Continue in Background Folytatás a háttérben
|
||||
Cancel Export Exportálás megszakítása
|
||||
@msg Export cancelled Az exportálás megszakítva
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Kívánt
|
||||
Word Spacing: Szóköz:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5172,6 +5172,12 @@ swatch Bright Violet Violet cerah
|
||||
Typing Mengetik
|
||||
Persistent Persisten
|
||||
Library Panel Options Opsi panel pustaka
|
||||
Artboard {n} of {total} Artboard {n} dari {total}
|
||||
Encoding and writing the file… Mengodekan dan menulis berkas…
|
||||
@plural {n} more export waiting|{n} more exports waiting {n} ekspor lagi menunggu
|
||||
Continue in Background Lanjutkan di latar belakang
|
||||
Cancel Export Batalkan ekspor
|
||||
@msg Export cancelled Ekspor dibatalkan
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Diinginkan
|
||||
Word Spacing: Spasi Kata:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 3768 and column 19.
|
@@ -4801,6 +4801,12 @@ lighting Light Luce
|
||||
@msg too many library panels were opened in this session in questa sessione sono stati aperti troppi pannelli libreria
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. L’immagine è di {_1} × {_2} pixel, più di {LARGE_TRACE_SIDE} su un lato: viene vettorializzata, ma più lentamente. Un predefinito a fedeltà più bassa o una copia più piccola dell’immagine è più veloce.
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). L’immagine è stata vettorializzata a {_1} × {_2} pixel, ridotta da {_3} × {_4} (al massimo {_5} megapixel).
|
||||
Artboard {n} of {total} Tavola da disegno {n} di {total}
|
||||
Encoding and writing the file… Codifica e scrittura del file…
|
||||
@plural {n} more export waiting|{n} more exports waiting {n} altra esportazione in attesa|{n} altre esportazioni in attesa
|
||||
Continue in Background Continua in background
|
||||
Cancel Export Annulla esportazione
|
||||
@msg Export cancelled Esportazione annullata
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Desiderato
|
||||
Word Spacing: Spaziatura parole:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4841,6 +4841,12 @@ lighting Light ライト
|
||||
@msg too many library panels were opened in this session このセッションで開いたライブラリパネルが多すぎます
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. 画像は {_1} × {_2} ピクセルで、一辺が {LARGE_TRACE_SIDE} を超えています。トレースはできますが、時間がかかります。忠実度の低いプリセットや画像の小さいコピーを使うと速くなります。
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). 画像は {_3} × {_4} から縮小した {_1} × {_2} ピクセルでトレースされました (最大 {_5} メガピクセル)。
|
||||
Artboard {n} of {total} アートボード {n} / {total}
|
||||
Encoding and writing the file… ファイルをエンコードして書き込み中…
|
||||
@plural {n} more export waiting|{n} more exports waiting 待機中の書き出し: あと {n} 件
|
||||
Continue in Background バックグラウンドで続行
|
||||
Cancel Export 書き出しをキャンセル
|
||||
@msg Export cancelled 書き出しをキャンセルしました
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired 最適
|
||||
Word Spacing: 単語間隔:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4764,6 +4764,12 @@ lighting Light 광원
|
||||
@msg too many library panels were opened in this session 이 세션에서 라이브러리 패널을 너무 많이 열었습니다
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. 이미지가 {_1} × {_2}픽셀로 한 변이 {LARGE_TRACE_SIDE}을 넘습니다. 추적은 되지만 더 느립니다. 충실도가 낮은 사전 설정이나 이미지의 작은 사본을 사용하면 더 빠릅니다.
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). 이미지를 {_3} × {_4}에서 축소한 {_1} × {_2}픽셀로 추적했습니다(최대 {_5}메가픽셀).
|
||||
Artboard {n} of {total} 아트보드 {n} / {total}
|
||||
Encoding and writing the file… 파일을 인코딩하고 쓰는 중…
|
||||
@plural {n} more export waiting|{n} more exports waiting 대기 중인 내보내기 {n}개 더
|
||||
Continue in Background 백그라운드에서 계속
|
||||
Cancel Export 내보내기 취소
|
||||
@msg Export cancelled 내보내기를 취소했습니다
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired 희망
|
||||
Word Spacing: 단어 간격:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5215,7 +5215,12 @@ swatch Bright Violet Felviolet
|
||||
@msg too many library panels were opened in this session er zijn in deze sessie te veel bibliotheekpanelen geopend
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. De afbeelding is {_1} × {_2} pixels, meer dan {LARGE_TRACE_SIDE} aan één zijde: ze wordt getraceerd, maar langzamer. Een voorinstelling met minder detail of een kleinere kopie van de afbeelding traceert sneller.
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). De afbeelding is getraceerd op {_1} × {_2} pixels, verkleind van {_3} × {_4} (maximaal {_5} megapixels).
|
||||
|
||||
Artboard {n} of {total} Tekengebied {n} van {total}
|
||||
Encoding and writing the file… Bestand coderen en schrijven…
|
||||
@plural {n} more export waiting|{n} more exports waiting Nog {n} export in de wachtrij|Nog {n} exports in de wachtrij
|
||||
Continue in Background Doorgaan op achtergrond
|
||||
Cancel Export Export annuleren
|
||||
@msg Export cancelled Export geannuleerd
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Gewenst
|
||||
Word Spacing: Woordafstand:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5040,6 +5040,12 @@ mark-style Japanese Japońskie
|
||||
@msg too many library panels were opened in this session w tej sesji otwarto zbyt wiele paneli bibliotek
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Obraz ma {_1} × {_2} pikseli, ponad {LARGE_TRACE_SIDE} na jednym boku: zostanie obrysowany, ale wolniej. Ustawienie wstępne o niższej wierności lub mniejsza kopia obrazu obrysuje się szybciej.
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Obraz obrysowano w rozdzielczości {_1} × {_2} pikseli, po zmniejszeniu z {_3} × {_4} (najwyżej {_5} megapikseli).
|
||||
Artboard {n} of {total} Obszar kompozycji {n} z {total}
|
||||
Encoding and writing the file… Kodowanie i zapisywanie pliku…
|
||||
@plural {n} more export waiting|{n} more exports waiting Czeka jeszcze {n} eksport|Czekają jeszcze {n} eksporty|Czeka jeszcze {n} eksportów
|
||||
Continue in Background Kontynuuj w tle
|
||||
Cancel Export Anuluj eksport
|
||||
@msg Export cancelled Eksport anulowany
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Optymalne
|
||||
Word Spacing: Odstępy między wyrazami:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5105,6 +5105,12 @@ mark-style Japanese Japonesas
|
||||
@msg too many library panels were opened in this session foram abertos painéis de biblioteca demais nesta sessão
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. A imagem tem {_1} × {_2} pixels, mais de {LARGE_TRACE_SIDE} em um lado: ela é traçada, mas mais devagar. Uma predefinição de menor fidelidade ou uma cópia menor da imagem traça mais rápido.
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). A imagem foi traçada em {_1} × {_2} pixels, reduzida de {_3} × {_4} (no máximo {_5} megapixels).
|
||||
Artboard {n} of {total} Mesa de Trabalho {n} de {total}
|
||||
Encoding and writing the file… Codificando e gravando o arquivo…
|
||||
@plural {n} more export waiting|{n} more exports waiting Mais {n} exportação aguardando|Mais {n} exportações aguardando
|
||||
Continue in Background Continuar em Segundo Plano
|
||||
Cancel Export Cancelar Exportação
|
||||
@msg Export cancelled Exportação cancelada
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Desejado
|
||||
Word Spacing: Espaçamento entre Palavras:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -4797,6 +4797,12 @@ lighting Light Свет
|
||||
@msg too many library panels were opened in this session в этом сеансе открыто слишком много панелей библиотек
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Изображение {_1} × {_2} пикселей, больше {LARGE_TRACE_SIDE} по одной стороне: трассировка выполняется, но медленнее. Стиль с меньшей точностью или уменьшенная копия изображения трассируются быстрее.
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Изображение трассировано в размере {_1} × {_2} пикселей, уменьшенное с {_3} × {_4} (не более {_5} мегапикселей).
|
||||
Artboard {n} of {total} Монтажная область {n} из {total}
|
||||
Encoding and writing the file… Кодирование и запись файла…
|
||||
@plural {n} more export waiting|{n} more exports waiting Ещё {n} экспорт в очереди|Ещё {n} экспорта в очереди|Ещё {n} экспортов в очереди
|
||||
Continue in Background Продолжить в фоне
|
||||
Cancel Export Отменить экспорт
|
||||
@msg Export cancelled Экспорт отменён
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Желательно
|
||||
Word Spacing: Интервал между словами:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5144,6 +5144,12 @@ theme Appearance Utseende
|
||||
@msg too many library panels were opened in this session för många bibliotekspaneler har öppnats i den här sessionen
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Bilden är {_1} × {_2} pixlar, mer än {LARGE_TRACE_SIDE} på en sida: den vektoriseras, men långsammare. En förinställning med lägre återgivning eller en mindre kopia av bilden går snabbare.
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Bilden vektoriserades vid {_1} × {_2} pixlar, nedskalad från {_3} × {_4} (högst {_5} megapixlar).
|
||||
Artboard {n} of {total} Rityta {n} av {total}
|
||||
Encoding and writing the file… Kodar och skriver filen…
|
||||
@plural {n} more export waiting|{n} more exports waiting {n} export till väntar|{n} exporter till väntar
|
||||
Continue in Background Fortsätt i bakgrunden
|
||||
Cancel Export Avbryt export
|
||||
@msg Export cancelled Exporten avbröts
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Önskat
|
||||
Word Spacing: Ordavstånd:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -4781,6 +4781,12 @@ lighting Light Світло
|
||||
@msg too many library panels were opened in this session у цьому сеансі відкрито забагато панелей бібліотек
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Зображення {_1} × {_2} пікселів, понад {LARGE_TRACE_SIDE} з одного боку: трасування виконується, але повільніше. Стиль із нижчою точністю або менша копія зображення трасуються швидше.
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Зображення трасовано в розмірі {_1} × {_2} пікселів, зменшене з {_3} × {_4} (щонайбільше {_5} мегапікселів).
|
||||
Artboard {n} of {total} Монтажна область {n} з {total}
|
||||
Encoding and writing the file… Кодування та запис файлу…
|
||||
@plural {n} more export waiting|{n} more exports waiting Ще {n} експорт в очікуванні|Ще {n} експорти в очікуванні|Ще {n} експортів в очікуванні
|
||||
Continue in Background Продовжити у фоні
|
||||
Cancel Export Скасувати експорт
|
||||
@msg Export cancelled Експорт скасовано
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Бажане
|
||||
Word Spacing: Інтервал між словами:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 725 and column 40.
|
@@ -4781,6 +4781,12 @@ theme Appearance Giao diện
|
||||
@msg too many library panels were opened in this session đã mở quá nhiều bảng thư viện trong phiên này
|
||||
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Ảnh có kích thước {_1} × {_2} điểm ảnh, hơn {LARGE_TRACE_SIDE} ở một cạnh: ảnh vẫn được dò, nhưng chậm hơn. Một thiết lập sẵn có độ trung thực thấp hơn hoặc một bản sao nhỏ hơn của ảnh sẽ dò nhanh hơn.
|
||||
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Ảnh đã được dò ở {_1} × {_2} điểm ảnh, thu nhỏ từ {_3} × {_4} (tối đa {_5} megapixel).
|
||||
Artboard {n} of {total} Bảng vẽ {n} / {total}
|
||||
Encoding and writing the file… Đang mã hóa và ghi tệp…
|
||||
@plural {n} more export waiting|{n} more exports waiting Còn {n} lượt xuất đang chờ
|
||||
Continue in Background Tiếp tục trong nền
|
||||
Cancel Export Hủy xuất
|
||||
@msg Export cancelled Đã hủy xuất
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired Mong muốn
|
||||
Word Spacing: Khoảng cách từ:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 3948 and column 10.
|
@@ -5023,7 +5023,12 @@ lighting Light 光源
|
||||
@msg only http, https and mailto URLs open in the browser, not “{url}” 浏览器中只会打开 http、https 和 mailto 的 URL,不会打开 “{url}”
|
||||
@msg {id} opens another app: only the person at the keyboard can run it {id} 会打开其他应用程序:只有使用键盘的人才能运行它
|
||||
@msg “{name}” is not a file “{name}” 不是文件
|
||||
|
||||
Artboard {n} of {total} 画板 {n} / {total}
|
||||
Encoding and writing the file… 正在编码并写入文件…
|
||||
@plural {n} more export waiting|{n} more exports waiting 另有 {n} 个导出等待中
|
||||
Continue in Background 在后台继续
|
||||
Cancel Export 取消导出
|
||||
@msg Export cancelled 已取消导出
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired 所需
|
||||
Word Spacing: 字间距:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4077,6 +4077,12 @@ lighting Light 光源
|
||||
Enable Guides for 9-Slice Scaling 啟用 9-slice 縮放參考線
|
||||
Persistent 持久
|
||||
Library Panel Options 資料庫面板選項
|
||||
Artboard {n} of {total} 工作區域 {n} / {total}
|
||||
Encoding and writing the file… 正在編碼並寫入檔案…
|
||||
@plural {n} more export waiting|{n} more exports waiting 另有 {n} 個匯出等待中
|
||||
Continue in Background 在背景繼續
|
||||
Cancel Export 取消匯出
|
||||
@msg Export cancelled 已取消匯出
|
||||
# Paragraph panel menu › Justification…
|
||||
Desired 所需
|
||||
Word Spacing: 字間距:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -3,7 +3,9 @@
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::SavedView;
|
||||
use vectorcraft_doc::cancel;
|
||||
use vectorcraft_engine::EngineError;
|
||||
use vectorcraft_engine::cmd::fileio::progress::{self, Progress};
|
||||
use vectorcraft_engine::cmd::fileio::{self, Format, SAVE_FORMATS, SaveMode, SavePlan, TEMPLATE_FORMATS};
|
||||
|
||||
use crate::background::{self, Writer};
|
||||
@@ -675,6 +677,25 @@ pub fn recent_files(app: &VectorcraftApp) -> &[String] {
|
||||
&r[..r.len().min(app.session.prefs.recent_files_count as usize)]
|
||||
}
|
||||
|
||||
/// The status once an export was cancelled.
|
||||
pub const EXPORT_CANCELLED: &str = "Export cancelled";
|
||||
|
||||
/// File → Cancel Export (the progress dialog's Cancel): every export running in the background
|
||||
/// stops and writes nothing more → `{cancelled: n}`.
|
||||
pub fn cancel_exports(app: &mut VectorcraftApp) -> Value {
|
||||
let n = app.background.cancel_exports();
|
||||
if n > 0 {
|
||||
app.status(EXPORT_CANCELLED);
|
||||
}
|
||||
json!({ "cancelled": n })
|
||||
}
|
||||
|
||||
/// The progress dialog's Continue in Background: the exports go on, shown in the status bar only
|
||||
/// → `{hidden: n}`.
|
||||
pub fn hide_exports(app: &mut VectorcraftApp) -> Value {
|
||||
json!({ "hidden": app.background.hide_exports() })
|
||||
}
|
||||
|
||||
/// Export the active document in `format` (default: the path's extension, else PNG) with the
|
||||
/// `document.export` options in `params` (artboard, range, useArtboards, selectedOnly, ppi, SVG
|
||||
/// options…): every file it writes (one per artboard, linked images) goes next to `path`. The
|
||||
@@ -704,18 +725,31 @@ pub fn export(app: &mut VectorcraftApp, format: Option<&str>, path: Option<Strin
|
||||
// Background Export: encoded and written from this snapshot while editing goes on.
|
||||
let label = format!("Exporting {}", fileio::file_name(&path));
|
||||
let (params, target) = (params.clone(), path.clone());
|
||||
// The progress dialog's counts and Cancel: a cancelled export stops drawing and never starts
|
||||
// writing.
|
||||
let progress = Progress::new();
|
||||
let (reporting, asked) = (progress.clone(), progress.clone());
|
||||
let work = move |write: Writer| {
|
||||
let enc = fileio::encode_all(&doc, f.id, ¶ms).map_err(|e| e.to_string())?;
|
||||
let files = write_encoded(write, &doc, &path, &enc)?;
|
||||
let mut out = json!({ "path": files.first().unwrap_or(&path), "warnings": enc.warnings });
|
||||
if files.len() > 1 {
|
||||
out["files"] = json!(files);
|
||||
}
|
||||
Ok(out)
|
||||
progress::scope(&reporting, || {
|
||||
cancel::check().map_err(|e| e.to_string())?;
|
||||
let enc = fileio::encode_all(&doc, f.id, ¶ms).map_err(|e| e.to_string())?;
|
||||
cancel::check().map_err(|e| e.to_string())?;
|
||||
let files = write_encoded(write, &doc, &path, &enc)?;
|
||||
let mut out = json!({ "path": files.first().unwrap_or(&path), "warnings": enc.warnings });
|
||||
if files.len() > 1 {
|
||||
out["files"] = json!(files);
|
||||
}
|
||||
Ok(out)
|
||||
})
|
||||
};
|
||||
let enabled = app.session.prefs.background_export;
|
||||
let failed = target.clone();
|
||||
let mut r = background::run(app, enabled, label, None, work, move |app, r| {
|
||||
let mut r = background::run_with_progress(app, enabled, label, None, Some(progress), work, move |app, r| {
|
||||
// A file written before the Cancel arrived is reported as written.
|
||||
if r.is_err() && asked.cancelled() {
|
||||
app.status(EXPORT_CANCELLED);
|
||||
return Ok(json!({ "cancelled": true }));
|
||||
}
|
||||
let r = r.inspect_err(|e| report_write_error(app, true, &failed, e))?;
|
||||
let what = match r["files"].as_array() {
|
||||
Some(files) => format!("{} files", files.len()),
|
||||
|
||||
@@ -25,6 +25,7 @@ pub mod cursors;
|
||||
pub mod dialogs;
|
||||
pub mod dock;
|
||||
mod edit_toolbar;
|
||||
pub mod export_progress;
|
||||
pub mod find_font;
|
||||
pub mod floating;
|
||||
pub mod font_menu;
|
||||
@@ -1440,6 +1441,7 @@ impl VectorcraftApp {
|
||||
dock::floating_panel(self, &ctx);
|
||||
floating::show(self, &ctx);
|
||||
dialogs::show(self, &ctx);
|
||||
export_progress::show(self, &ctx);
|
||||
palette::show(self, &ctx);
|
||||
if self.custom_titlebar {
|
||||
titlebar::resize_zones(ui);
|
||||
|
||||
@@ -747,6 +747,18 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
("file.openRecent29", "Open Recent File 29", "", "{}"),
|
||||
("file.openRecent30", "Open Recent File 30", "", "{}"),
|
||||
("file.reveal", "Show in Folder", "", "{} show the document's file in the system file manager (desktop) → {path}"),
|
||||
(
|
||||
"file.cancelExport",
|
||||
"Cancel Export",
|
||||
"",
|
||||
"{} stop every export running in the background (Background Export): an export stops between artboards and writes no file once cancelled → {cancelled: n}",
|
||||
),
|
||||
(
|
||||
"file.exportInBackground",
|
||||
"Continue in Background",
|
||||
"",
|
||||
"{} close the export progress dialog; the exports go on, shown in the status bar → {hidden: n}",
|
||||
),
|
||||
(
|
||||
"ui.dxfOptionsDialog",
|
||||
"DXF Options Dialog",
|
||||
@@ -1101,6 +1113,8 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
|
||||
"file.newFromTemplate" if p.get("dataBase64").is_none() => io::new_from_template(app, s("path")),
|
||||
"file.revert" if p.get("confirmed").and_then(Value::as_bool) != Some(true) => io::ask_revert(app),
|
||||
"file.reveal" => io::reveal(app),
|
||||
"file.cancelExport" => Ok(io::cancel_exports(app)),
|
||||
"file.exportInBackground" => Ok(io::hide_exports(app)),
|
||||
id if id.starts_with("file.openRecent") => match recent_slot(app, id).cloned() {
|
||||
Some(path) => io::open_reporting(app, &path),
|
||||
None => Err("no such recent file".into()),
|
||||
@@ -2225,6 +2239,7 @@ pub fn enabled(app: &VectorcraftApp, id: &str) -> bool {
|
||||
"type.bold" | "type.italic" => crate::panels::character::text_style(app).is_some(),
|
||||
id if crate::panels::paragraph::ALIGN_COMMANDS.iter().any(|c| c.0 == id) => crate::panels::character::text_style(app).is_some(),
|
||||
"file.reveal" => app.services.reveal.is_some() && app.session.active().is_some_and(|d| d.path.is_some()),
|
||||
"file.cancelExport" | "file.exportInBackground" => app.background.jobs.iter().any(|j| j.progress.is_some() && !j.cancelled()),
|
||||
"file.place"
|
||||
| "file.export.svg"
|
||||
| "file.export.png"
|
||||
|
||||
@@ -121,6 +121,34 @@ fn exports_run_in_the_background_and_the_preference_turns_both_off() {
|
||||
assert!(app.background.jobs.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_cancelled_export_writes_nothing() {
|
||||
let (gate, writing) = (Gate::default(), Gate::default());
|
||||
let files = Files::default();
|
||||
let (f, g, w) = (files.clone(), gate.clone(), writing.clone());
|
||||
let services = Services {
|
||||
write_shared: Some(Arc::new(move |p: &str, b: &[u8]| {
|
||||
w.open();
|
||||
g.wait();
|
||||
f.lock().unwrap().push((p.to_string(), b.to_vec()));
|
||||
Ok(())
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
let mut app = VectorcraftApp::new(Session::new(), services);
|
||||
app.run("file.new", json!({"width": 100, "height": 100})).unwrap();
|
||||
// The first export holds the worker at the gate, past its last check: it still writes.
|
||||
io::export(&mut app, Some("svg"), Some("/out/first.svg".into()), &json!({})).unwrap();
|
||||
writing.wait();
|
||||
io::export(&mut app, Some("png"), Some("/out/second.png".into()), &json!({})).unwrap();
|
||||
assert_eq!(app.run("file.cancelExport", json!({})).unwrap()["cancelled"], 2);
|
||||
assert_eq!(app.run("file.cancelExport", json!({})).unwrap()["cancelled"], 0, "nothing left to cancel");
|
||||
gate.open();
|
||||
background::wait_all(&mut app);
|
||||
assert_eq!(written(&files), ["/out/first.svg"]);
|
||||
assert_eq!(app.ui.status, io::EXPORT_CANCELLED);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quitting_waits_for_background_saves() {
|
||||
let gate = Gate::default();
|
||||
|
||||
+3
-1
File diff suppressed because one or more lines are too long
+3
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user