Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions crates/ui-egui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1664,6 +1664,7 @@ impl NoteCraftApp {
return;
}
}
self.tell_kept_ink();
if let Some(ed) = self.editor.as_mut() {
ed.blocks.retain(|b| !matches!(b, Block::Image { src, .. } if crate::inkpad::is_old_sheet(src)));
ed.rebuild_body();
Expand Down Expand Up @@ -3856,6 +3857,7 @@ impl NoteCraftApp {
Ok(()) => self.say(crate::i18n::format_message_id("ui.took_the_last_stroke_back_count_left", &[("count", &left.to_string())])),
Err(e) => self.complain(&crate::i18n::format_message_id("ui.cannot_write_the_drawing_error", &[("error", &e.to_string())])),
}
self.tell_kept_ink();
// The page stops naming ink it no longer has.
self.sync_ink_reference();
}
Expand Down Expand Up @@ -4190,6 +4192,15 @@ impl NoteCraftApp {
self.say(crate::tl_id!("ui.write_anywhere_on_the_page"));
}

/// Says where the page's ink file was kept, when writing the ink kept it first because it
/// held more than could be read back as strokes (issue #157).
fn tell_kept_ink(&mut self) {
let Some(kept) = self.pad.as_mut().and_then(|pad| pad.kept_original.take()) else { return };
let shown = self.shown(&kept);
log::warn!("the page's ink file held more than its strokes; the original was kept: {kept:?}");
self.say(crate::i18n::format_message_id("ui.kept_the_original_drawing_file_path", &[("path", &shown)]));
}

/// Writes the drawing again with no pen readings in it, for sending somewhere else.
fn ink_export_plain(&mut self) {
let Some(pad) = self.pad.as_ref() else {
Expand Down Expand Up @@ -6026,6 +6037,7 @@ impl NoteCraftApp {
Ok(()) => self.say(crate::i18n::format_message_id("ui.drawing_saved_count_strokes", &[("count", &strokes.to_string())])),
Err(e) => self.complain(&crate::i18n::format_message_id("ui.cannot_write_the_drawing_error", &[("error", &e.to_string())])),
}
self.tell_kept_ink();
self.sync_ink_reference();
}
if let Some(href) = follow {
Expand Down
85 changes: 83 additions & 2 deletions crates/ui-egui/src/inkpad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ pub struct Pad {
pub dirty: bool,
/// How many strokes in the file were a picture only, with no readings to give back.
pub flat: usize,
/// The file holds more than `drawing` does: it could not be read, or some of its lines are
/// a picture only. Before the file is first written over, it is kept beside the ink.
pub incomplete: bool,
/// Where that file was kept, once it has been, for the window to say so.
pub kept_original: Option<PathBuf>,
/// The stroke being drawn now, if the pen is down.
drawing_now: Option<Stroke>,
/// The strokes being held, by their place in the drawing. Never written to the file:
Expand Down Expand Up @@ -128,13 +133,21 @@ impl Pad {
/// drawn on, so a page nobody has drawn on stays as clean as it was.
pub fn of_page(page_dir: &Path) -> Pad {
let file = page_dir.join(FILE);
let (drawing, flat) =
std::fs::read_to_string(&file).ok().and_then(|text| svg::read(&text).ok()).unwrap_or_else(|| (Drawing::new(SHEET_W, SHEET_H), 0));
let read = std::fs::read_to_string(&file).ok().and_then(|text| svg::read(&text).ok());
// A file that is there but could not be read, or that holds lines with no readings,
// would lose them the first time the ink is written (issue #157).
let incomplete = match &read {
Some((_, flat)) => *flat > 0,
None => file.exists(),
};
let (drawing, flat) = read.unwrap_or_else(|| (Drawing::new(SHEET_W, SHEET_H), 0));
Pad {
file,
drawing,
dirty: false,
flat,
incomplete,
kept_original: None,
drawing_now: None,
held: Vec::new(),
loop_now: Vec::new(),
Expand All @@ -154,6 +167,13 @@ impl Pad {
if let Some(parent) = self.file.parent() {
std::fs::create_dir_all(parent)?;
}
// The file holds what this drawing does not: it is kept, as it was, before it is
// written over or taken away, and only then (issue #157).
if self.incomplete && self.file.is_file() {
let kept = self.keep_original()?;
self.kept_original = Some(kept);
}
self.incomplete = false;
// A page drawn on and then rubbed clean loses its file rather than keeping an
// empty one, so a page with no ink has no ink in its folder either.
if self.drawing.is_empty() {
Expand All @@ -169,6 +189,26 @@ impl Pad {
Ok(())
}

/// Copies the ink file as it is to `ink.svg.bak`, or the first free `ink-N.svg.bak`, beside
/// it. Not an `.svg`, so nothing takes the copy for the page's ink or for a picture of it.
fn keep_original(&self) -> std::io::Result<PathBuf> {
let folder = self.file.parent().map(Path::to_path_buf).unwrap_or_default();
for n in 1usize..=10_000 {
let name = if n == 1 { format!("{FILE}.bak") } else { format!("ink-{n}.svg.bak") };
let kept = folder.join(name);
match std::fs::OpenOptions::new().write(true).create_new(true).open(&kept) {
Ok(mut out) => {
let mut original = std::fs::File::open(&self.file)?;
std::io::copy(&mut original, &mut out)?;
return Ok(kept);
}
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue,
Err(e) => return Err(e),
}
}
Err(std::io::Error::new(std::io::ErrorKind::AlreadyExists, "every name for keeping the original ink file is taken"))
}

/// Writes it again as a plain picture, beside the ink, for sending somewhere else.
/// Returns where it was written.
pub fn save_plain(&self) -> std::io::Result<PathBuf> {
Expand Down Expand Up @@ -836,6 +876,47 @@ mod tests {
assert!(pad.file.ends_with(FILE), "the ink sits beside the page: {}", pad.file.display());
}

/// Issue #157: an ink file that holds lines with no readings, or that cannot be read at all,
/// is kept as it was before new ink is written over it, and only once.
#[test]
fn ink_that_cannot_all_be_read_is_kept_before_it_is_written_over() {
let original = "<svg xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M10 10 L50 50\" stroke=\"red\"/></svg>";
for (label, text) in [("a picture's path", original), ("not an SVG at all", "this is not a drawing")] {
let t = TempDir::new("ink-keep-original");
let page = t.path().to_path_buf();
std::fs::write(page.join(FILE), text).expect("an ink file of someone else's");
let mut pad = Pad::of_page(&page);
assert!(pad.incomplete, "{label}: the file holds more than the strokes read from it");

assert!(pad.drawing.add(stroke(10.0)));
pad.dirty = true;
pad.save().expect("saved");
let kept = pad.kept_original.take().expect("the original was kept");
assert_eq!(kept, page.join("ink.svg.bak"), "{label}");
assert_eq!(std::fs::read_to_string(&kept).expect("kept"), text, "{label}: kept as it was");
assert!(!crate::inkpad::is_old_sheet("ink.svg.bak"));

// Saving again does not keep another copy: the file is NoteCraft's own now.
assert!(pad.drawing.add(stroke(20.0)));
pad.save().expect("saved again");
assert_eq!(pad.kept_original, None, "{label}");
assert!(!page.join("ink-2.svg.bak").exists(), "{label}");
}

// A file of NoteCraft's own, read whole, is written over as before, with nothing kept.
let t = TempDir::new("ink-keep-own");
let page = t.path().to_path_buf();
let mut pad = Pad::of_page(&page);
assert!(pad.drawing.add(stroke(10.0)));
pad.save().expect("saved");
let mut again = Pad::of_page(&page);
assert!(!again.incomplete);
assert!(again.drawing.add(stroke(30.0)));
again.save().expect("saved");
assert_eq!(again.kept_original, None);
assert!(!page.join("ink.svg.bak").exists());
}

#[test]
fn ink_is_written_beside_the_page_and_comes_back_as_strokes() {
let t = TempDir::new("ink-layer-save");
Expand Down
1 change: 1 addition & 0 deletions i18n/ar-XB.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@
@id ui.json [[RLI]]$$$JSON@@@[[PDI]]
@id ui.justification_goes_on_a_paragraph_of_text [[RLI]]$$$Justification goes on a paragraph of text@@@[[PDI]]
@id ui.keep_this_window_above_the_others [[RLI]]$$$Keep this window above the others@@@[[PDI]]
@id ui.kept_the_original_drawing_file_path [[RLI]]$$$This page's drawing file held lines NoteCraft cannot edit. The file as it was is kept as {path}.@@@[[PDI]]
@id ui.label [[RLI]]$$$label@@@[[PDI]]
@id ui.label_not_built [[RLI]]$$${label} (not built)@@@[[PDI]]
@id ui.label_not_built_yet [[RLI]]$$${label}: not built yet@@@[[PDI]]
Expand Down
1 change: 1 addition & 0 deletions i18n/ar.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@
@id ui.json JSON
@id ui.justification_goes_on_a_paragraph_of_text التبرير ينطبق على فقرة نصية
@id ui.keep_this_window_above_the_others إبقاء هذه النافذة فوق النوافذ الأخرى
@id ui.kept_the_original_drawing_file_path احتوى ملف الرسم في هذه الصفحة على خطوط لا يستطيع NoteCraft تحريرها. تم الاحتفاظ بالملف الأصلي باسم {path}.
@id ui.label تسمية
@id ui.label_not_built {label} (غير مبنٍ)
@id ui.label_not_built_yet {label}: لم يُبنَ بعد
Expand Down
1 change: 1 addition & 0 deletions i18n/de.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@
@id ui.json JSON
@id ui.justification_goes_on_a_paragraph_of_text Der Textausrichtung bezieht sich auf einen Absatz
@id ui.keep_this_window_above_the_others Dieses Fenster immer im Vordergrund halten
@id ui.kept_the_original_drawing_file_path Die Zeichnungsdatei dieser Seite enthielt Linien, die NoteCraft nicht bearbeiten kann. Die ursprüngliche Datei ist als {path} aufbewahrt.
@id ui.label Beschriftung
@id ui.label_not_built {label} (nicht implementiert)
@id ui.label_not_built_yet {label}: noch nicht implementiert
Expand Down
1 change: 1 addition & 0 deletions i18n/en-XA.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@
@id ui.json $$$JSON@@@
@id ui.justification_goes_on_a_paragraph_of_text $$$Justification goes on a paragraph of text@@@
@id ui.keep_this_window_above_the_others $$$Keep this window above the others@@@
@id ui.kept_the_original_drawing_file_path $$$This page's drawing file held lines NoteCraft cannot edit. The file as it was is kept as {path}.@@@
@id ui.label $$$label@@@
@id ui.label_not_built $$${label} (not built)@@@
@id ui.label_not_built_yet $$${label}: not built yet@@@
Expand Down
1 change: 1 addition & 0 deletions i18n/en.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@
@id ui.json JSON
@id ui.justification_goes_on_a_paragraph_of_text Justification goes on a paragraph of text
@id ui.keep_this_window_above_the_others Keep this window above the others
@id ui.kept_the_original_drawing_file_path This page's drawing file held lines NoteCraft cannot edit. The file as it was is kept as {path}.
@id ui.label label
@id ui.label_not_built {label} (not built)
@id ui.label_not_built_yet {label}: not built yet
Expand Down
1 change: 1 addition & 0 deletions i18n/es.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@
@id ui.json JSON
@id ui.justification_goes_on_a_paragraph_of_text La justificación se aplica a un párrafo de texto
@id ui.keep_this_window_above_the_others Mantener esta ventana por encima de las demás
@id ui.kept_the_original_drawing_file_path El archivo de dibujo de esta página tenía trazos que NoteCraft no puede editar. El archivo original se conserva como {path}.
@id ui.label label
@id ui.label_not_built {label} (no compilado)
@id ui.label_not_built_yet {label}: aún no compilado
Expand Down
1 change: 1 addition & 0 deletions i18n/fa.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@
@id ui.json JSON
@id ui.justification_goes_on_a_paragraph_of_text تراز متن در یک بند اعمال می‌شود
@id ui.keep_this_window_above_the_others این پنجره را بالای سایر پنجره‌ها نگه دارید
@id ui.kept_the_original_drawing_file_path فایل طراحی این صفحه خط‌هایی داشت که NoteCraft نمی‌تواند ویرایش کند. فایل اصلی با نام {path} نگه داشته شد.
@id ui.label برچسب
@id ui.label_not_built {label} (ساخته نشده)
@id ui.label_not_built_yet {label}: هنوز ساخته نشده
Expand Down
1 change: 1 addition & 0 deletions i18n/fr.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@
@id ui.json JSON
@id ui.justification_goes_on_a_paragraph_of_text La justification s'applique à un paragraphe de texte
@id ui.keep_this_window_above_the_others Garder cette fenêtre au-dessus des autres
@id ui.kept_the_original_drawing_file_path Le fichier de dessin de cette page contenait des traits que NoteCraft ne peut pas modifier. Le fichier d’origine est conservé sous {path}.
@id ui.label label
@id ui.label_not_built {label} (non implémenté)
@id ui.label_not_built_yet {label} : pas encore implémenté
Expand Down
1 change: 1 addition & 0 deletions i18n/he.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@
@id ui.json JSON
@id ui.justification_goes_on_a_paragraph_of_text יישור מתייחס לפסקת טקסט
@id ui.keep_this_window_above_the_others החזק חלון זה מעל האחרים
@id ui.kept_the_original_drawing_file_path קובץ הציור של דף זה הכיל קווים ש-NoteCraft אינו יכול לערוך. הקובץ המקורי נשמר בשם {path}.
@id ui.label תווית
@id ui.label_not_built {label} (לא בנוי)
@id ui.label_not_built_yet {label}: טרם נבנה
Expand Down
1 change: 1 addition & 0 deletions i18n/hi.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@
@id ui.json JSON
@id ui.justification_goes_on_a_paragraph_of_text पंक्तिबद्धता पाठ के पैराग्राफ पर लागू होती है
@id ui.keep_this_window_above_the_others इस विंडो को अन्य विंडोओं से ऊपर रखें
@id ui.kept_the_original_drawing_file_path इस पेज की ड्रॉइंग फ़ाइल में ऐसी रेखाएँ थीं जिन्हें NoteCraft संपादित नहीं कर सकता। मूल फ़ाइल {path} के रूप में रखी गई है।
@id ui.label लेबल
@id ui.label_not_built {label} (बिल्ड नहीं हुआ)
@id ui.label_not_built_yet {label}: अभी तक बिल्ड नहीं हुआ
Expand Down
1 change: 1 addition & 0 deletions i18n/ja.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@
@id ui.json JSON
@id ui.justification_goes_on_a_paragraph_of_text 段落のテキストに対して適用されます
@id ui.keep_this_window_above_the_others このウィンドウを他のウィンドウの上に固定する
@id ui.kept_the_original_drawing_file_path このページの描画ファイルには NoteCraft で編集できない線が含まれていました。元のファイルは {path} として保存されています。
@id ui.label ラベル
@id ui.label_not_built {label} (未実装)
@id ui.label_not_built_yet {label}: まだ実装されていません
Expand Down
1 change: 1 addition & 0 deletions i18n/ko.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@
@id ui.json JSON
@id ui.justification_goes_on_a_paragraph_of_text 정렬은 텍스트 단락에 적용됩니다
@id ui.keep_this_window_above_the_others 이 창을 다른 창 위에 고정
@id ui.kept_the_original_drawing_file_path 이 페이지의 그리기 파일에 NoteCraft에서 편집할 수 없는 선이 있었습니다. 원래 파일은 {path}(으)로 보관되었습니다.
@id ui.label 레이블
@id ui.label_not_built {label} (미구현)
@id ui.label_not_built_yet {label}: 아직 구현되지 않음
Expand Down
1 change: 1 addition & 0 deletions i18n/ru.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@
@id ui.json JSON
@id ui.justification_goes_on_a_paragraph_of_text Выравнивание применяется к абзацу текста
@id ui.keep_this_window_above_the_others Держать это окно поверх остальных
@id ui.kept_the_original_drawing_file_path Файл рисунка этой страницы содержал линии, которые NoteCraft не может изменить. Исходный файл сохранён как {path}.
@id ui.label метка
@id ui.label_not_built {label} (не реализовано)
@id ui.label_not_built_yet {label}: ещё не реализовано
Expand Down
1 change: 1 addition & 0 deletions i18n/ur.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@
@id ui.json JSON
@id ui.justification_goes_on_a_paragraph_of_text جسٹیفیکیشن متن کے ایک پیراگراف پر لاگو ہوتی ہے
@id ui.keep_this_window_above_the_others اس ونڈو کو دوسروں کے اوپر رکھیں
@id ui.kept_the_original_drawing_file_path اس صفحے کی ڈرائنگ فائل میں ایسی لکیریں تھیں جنہیں NoteCraft مدون نہیں کر سکتا۔ اصل فائل {path} کے نام سے محفوظ رکھی گئی ہے۔
@id ui.label لیبل
@id ui.label_not_built {label} (بنایا نہیں گیا)
@id ui.label_not_built_yet {label}: ابھی تک نہیں بنایا گیا
Expand Down
1 change: 1 addition & 0 deletions i18n/zh.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@
@id ui.json JSON
@id ui.justification_goes_on_a_paragraph_of_text 对齐方式应用于一段文本
@id ui.keep_this_window_above_the_others 保持此窗口位于其他窗口之上
@id ui.kept_the_original_drawing_file_path 此页面的绘图文件包含 NoteCraft 无法编辑的线条。原始文件已保存为 {path}。
@id ui.label 标签
@id ui.label_not_built {label}(未构建)
@id ui.label_not_built_yet {label}:尚未构建
Expand Down
Loading