mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-05 01:59:30 +00:00
feat: major Acrobat UI parity, crop, and layer fixes
This commit is contained in:
1 parent
d3a40f2ac1
commit
293faa0c05
4 files changed
+64
-63
No files matched your search
@@ -561,21 +561,21 @@ pub(crate) fn format_panel(ui: &mut egui::Ui, t: &Tokens, style: &AddedText) ->
|
||||
ui.selectable_value(&mut s.family, f, f.label());
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Some(installed) = crate::files::installed_font_files(ui.ctx()) {
|
||||
if !installed.is_empty() {
|
||||
ui.separator();
|
||||
ui.label(egui::RichText::new(tl!("Installed fonts")).weak().small());
|
||||
for font in &installed {
|
||||
let fam = if font.family.eq_ignore_ascii_case("times") || font.face.to_ascii_lowercase().contains("times") {
|
||||
FontFamily::Times
|
||||
} else if font.family.eq_ignore_ascii_case("courier") || font.face.to_ascii_lowercase().contains("courier") {
|
||||
FontFamily::Courier
|
||||
} else {
|
||||
FontFamily::Helvetica
|
||||
};
|
||||
if ui.selectable_label(s.family == fam, &font.face).clicked() {
|
||||
s.family = fam;
|
||||
}
|
||||
if let Some(installed) = crate::files::installed_font_files(ui.ctx())
|
||||
&& !installed.is_empty()
|
||||
{
|
||||
ui.separator();
|
||||
ui.label(egui::RichText::new(tl!("Installed fonts")).weak().small());
|
||||
for font in &installed {
|
||||
let fam = if font.family.eq_ignore_ascii_case("times") || font.face.to_ascii_lowercase().contains("times") {
|
||||
FontFamily::Times
|
||||
} else if font.family.eq_ignore_ascii_case("courier") || font.face.to_ascii_lowercase().contains("courier") {
|
||||
FontFamily::Courier
|
||||
} else {
|
||||
FontFamily::Helvetica
|
||||
};
|
||||
if ui.selectable_label(s.family == fam, &font.face).clicked() {
|
||||
s.family = fam;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -621,10 +621,10 @@ pub(crate) fn format_panel(ui: &mut egui::Ui, t: &Tokens, style: &AddedText) ->
|
||||
}
|
||||
});
|
||||
let show_palette_id = ui.id().with("show-palette-added");
|
||||
if ui.data(|d| d.get_temp::<bool>(show_palette_id)).unwrap_or(false) {
|
||||
if let Some(picked) = crate::comments::swatch_grid(ui, Some(s.color)) {
|
||||
s.color = picked;
|
||||
}
|
||||
if ui.data(|d| d.get_temp::<bool>(show_palette_id)).unwrap_or(false)
|
||||
&& let Some(picked) = crate::comments::swatch_grid(ui, Some(s.color))
|
||||
{
|
||||
s.color = picked;
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
let b_resp = ui.selectable_label(s.bold, egui::RichText::new("T").strong().size(14.0));
|
||||
|
||||
@@ -179,9 +179,7 @@ pub fn toggle_bullet_list(text: &mut String) -> bool {
|
||||
} else {
|
||||
new_text.push_str(line);
|
||||
}
|
||||
} else if trimmed.is_empty() {
|
||||
new_text.push_str(line);
|
||||
} else if trimmed.starts_with("• ") {
|
||||
} else if trimmed.is_empty() || trimmed.starts_with("• ") {
|
||||
new_text.push_str(line);
|
||||
} else {
|
||||
new_text.push_str("• ");
|
||||
@@ -218,11 +216,13 @@ pub fn toggle_numbered_list(text: &mut String) -> bool {
|
||||
}
|
||||
let t = line.trim_start();
|
||||
if all_numbered {
|
||||
if let Some(dot_idx) = t.find('.') {
|
||||
if dot_idx > 0 && t[..dot_idx].chars().all(|c| c.is_ascii_digit()) && t[dot_idx..].starts_with(". ") {
|
||||
new_text.push_str(&t[dot_idx + 2..]);
|
||||
continue;
|
||||
}
|
||||
if let Some(dot_idx) = t.find('.')
|
||||
&& dot_idx > 0
|
||||
&& t[..dot_idx].chars().all(|c| c.is_ascii_digit())
|
||||
&& t[dot_idx..].starts_with(". ")
|
||||
{
|
||||
new_text.push_str(&t[dot_idx + 2..]);
|
||||
continue;
|
||||
}
|
||||
new_text.push_str(line);
|
||||
} else if t.is_empty() {
|
||||
@@ -905,10 +905,11 @@ pub(crate) fn image_input(
|
||||
painter.rect_stroke(b, CornerRadius::ZERO, Stroke::new(1.5, ACCENT), egui::StrokeKind::Outside);
|
||||
draw_handles(b);
|
||||
}
|
||||
if allow_interaction && let Some(p) = hover_pos {
|
||||
if let Some(&h) = all_handles.iter().find(|&&h| handle_pos(b, h).distance(p) < 8.0) {
|
||||
ui.ctx().set_cursor_icon(cursor_for(h));
|
||||
}
|
||||
if allow_interaction
|
||||
&& let Some(p) = hover_pos
|
||||
&& let Some(&h) = all_handles.iter().find(|&&h| handle_pos(b, h).distance(p) < 8.0)
|
||||
{
|
||||
ui.ctx().set_cursor_icon(cursor_for(h));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -927,10 +928,10 @@ pub(crate) fn image_input(
|
||||
if let Some(s) = view.image_selection.as_mut() {
|
||||
s.drag = Some(ImageDrag { start: o, target: DragTarget::CropHandle(h), initial_rect: active_crop });
|
||||
}
|
||||
} else if active_crop.contains(o) {
|
||||
if let Some(s) = view.image_selection.as_mut() {
|
||||
s.drag = Some(ImageDrag { start: o, target: DragTarget::Move, initial_rect: active_crop });
|
||||
}
|
||||
} else if active_crop.contains(o)
|
||||
&& let Some(s) = view.image_selection.as_mut()
|
||||
{
|
||||
s.drag = Some(ImageDrag { start: o, target: DragTarget::Move, initial_rect: active_crop });
|
||||
}
|
||||
} else {
|
||||
let hit_handle = all_handles.iter().copied().find(|&h| handle_pos(b, h).distance(o) < 8.0);
|
||||
@@ -938,10 +939,10 @@ pub(crate) fn image_input(
|
||||
if let Some(s) = view.image_selection.as_mut() {
|
||||
s.drag = Some(ImageDrag { start: o, target: DragTarget::Handle(h), initial_rect: b });
|
||||
}
|
||||
} else if image_contains(i, o) {
|
||||
if let Some(s) = view.image_selection.as_mut() {
|
||||
s.drag = Some(ImageDrag { start: o, target: DragTarget::Move, initial_rect: b });
|
||||
}
|
||||
} else if image_contains(i, o)
|
||||
&& let Some(s) = view.image_selection.as_mut()
|
||||
{
|
||||
s.drag = Some(ImageDrag { start: o, target: DragTarget::Move, initial_rect: b });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -991,10 +992,10 @@ pub(crate) fn image_input(
|
||||
if let Some(s) = view.image_selection.as_mut() {
|
||||
s.crop_rect = Some(new_crop);
|
||||
}
|
||||
if resp.drag_stopped() || !ui.input(|i| i.pointer.primary_down()) {
|
||||
if let Some(s) = view.image_selection.as_mut() {
|
||||
s.drag = None;
|
||||
}
|
||||
if (resp.drag_stopped() || !ui.input(|i| i.pointer.primary_down()))
|
||||
&& let Some(s) = view.image_selection.as_mut()
|
||||
{
|
||||
s.drag = None;
|
||||
}
|
||||
ui.ctx().set_cursor_icon(match drag.target {
|
||||
DragTarget::CropHandle(h) => cursor_for(h),
|
||||
|
||||
@@ -400,18 +400,18 @@ fn format_section(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
let is_current = ed.font_resource.as_ref().is_some_and(|r| {
|
||||
font_choices.iter().any(|f| &f.resource == r && f.face == font.face)
|
||||
});
|
||||
if ui.selectable_label(is_current, &font.face).clicked() {
|
||||
if let Ok(bytes) = std::fs::read(&font.path) {
|
||||
let edit = pdfcraft_engine::Edit::RegisterTextFont { page: ed.page, bytes: std::sync::Arc::new(bytes.clone()) };
|
||||
if app.apply_edit(edit) {
|
||||
if let Some(doc) = app.session.get(app.views[i].id) {
|
||||
if let Some(f) = doc.text_editor_fonts(ed.page).into_iter().find(|f| f.bytes == bytes) {
|
||||
ed.font_resource = Some(f.resource);
|
||||
changed = true;
|
||||
}
|
||||
ed.fonts = crate::edit_text_ui::install_source_fonts(ui.ctx(), doc, ed.page);
|
||||
}
|
||||
if ui.selectable_label(is_current, &font.face).clicked()
|
||||
&& let Ok(bytes) = std::fs::read(&font.path)
|
||||
{
|
||||
let edit = pdfcraft_engine::Edit::RegisterTextFont { page: ed.page, bytes: std::sync::Arc::new(bytes.clone()) };
|
||||
if app.apply_edit(edit)
|
||||
&& let Some(doc) = app.session.get(app.views[i].id)
|
||||
{
|
||||
if let Some(f) = doc.text_editor_fonts(ed.page).into_iter().find(|f| f.bytes == bytes) {
|
||||
ed.font_resource = Some(f.resource);
|
||||
changed = true;
|
||||
}
|
||||
ed.fonts = crate::edit_text_ui::install_source_fonts(ui.ctx(), doc, ed.page);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -475,11 +475,11 @@ fn format_section(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
|
||||
// Color Swatch Grid (when toggled or open)
|
||||
let show_palette_id = ui.id().with("show-palette");
|
||||
if ui.data(|d| d.get_temp::<bool>(show_palette_id)).unwrap_or(false) {
|
||||
if let Some(picked) = crate::comments::swatch_grid(ui, Some(ed.look.color)) {
|
||||
ed.look.color = picked;
|
||||
changed = true;
|
||||
}
|
||||
if ui.data(|d| d.get_temp::<bool>(show_palette_id)).unwrap_or(false)
|
||||
&& let Some(picked) = crate::comments::swatch_grid(ui, Some(ed.look.color))
|
||||
{
|
||||
ed.look.color = picked;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Formatting Buttons: Bold, Italic, Underline, Superscript, Subscript
|
||||
|
||||
@@ -2127,11 +2127,11 @@ title = "Arrange objects (bring forward, send backward)"
|
||||
area = "C"
|
||||
tier = "P1"
|
||||
milestone = "M7"
|
||||
status = "planned"
|
||||
status = "partial"
|
||||
commands = []
|
||||
tools = []
|
||||
evidence = []
|
||||
notes = ""
|
||||
tools = ["image_edit"]
|
||||
evidence = ["crates/edit/src/tests.rs::image_arrange_stacking_order_and_remove_crop"]
|
||||
notes = "Image stacking order in content streams: Bring to Front, Send to Back, Bring Forward, Send Backward with undo/redo and PDF export persistence. Image-level crop and 8-point handle transforms. Still missing: arbitrary vector paths, inline images, and multi-object grouping."
|
||||
|
||||
[[feature]]
|
||||
id = "edit.align-distribute-objects"
|
||||
|
||||
Reference in new issue
Block a user