mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-14 19:27:07 -04:00
V: rotate the spread view in quarter turns
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
5bee68cf88
commit
7cd4657261
5 files changed
+157
-29
No files matched your search
@@ -102,6 +102,7 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope
|
||||
- Preferences: Dictionary (user dictionary; `spelling.setWords/words`), Spelling (Dynamic Spelling: red squiggles on the canvas, also Edit ▸ Spelling), Autocorrect (word list, applied as you type), Notes, Track Changes, Story Editor Display and File Handling (recovery interval) sections.
|
||||
- Colour management exposed: Edit ▸ Color Settings (`color.settings`: RGB/CMYK working spaces, intent, black-point compensation; `color.loadProfile` for ICC files; `color.convert` through the working spaces, with gamut checks) and View ▸ Proof Setup / Proof Colors (press, sRGB, legacy Mac RGB, colour-blindness simulations, simulate paper) on the canvas.
|
||||
- Keyboard: Tab / Shift+Tab hide and show all panels / all but Tools (`window.hidePanels`, `window.hidePanelsExceptTools`); Cmd+F6 / Cmd+Shift+F6 cycle documents; panel fields are reached with Tab and Escape returns to the layout.
|
||||
- View ▸ Rotate Spread (`view.rotateSpread`: 90° CW / CCW, 180°, clear): the view turns in quarter turns about its centre; tools, hit testing, the render texture and overlays follow; rulers hide while rotated. InDesign turns just the one spread.
|
||||
- Drag and drop text editing: drag selected text to move it (Alt copies) with its formatting (`text.release`).
|
||||
- Image Import Options: place any page of a multi-page PDF (`file.place {pdfPage}`; the app asks which page), shown on screen and embedded as that page in PDF export.
|
||||
- Define Lists: named numbered lists that continue across stories in page order (`list.define`, paragraph `listName` / `startAt`), round-tripped through IDML; `null` in `type.para` / `type.char` now removes an override.
|
||||
|
||||
@@ -16,19 +16,51 @@ pub const RULER: f32 = 15.0;
|
||||
/// Canvas ↔ screen transform.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Xf {
|
||||
/// Top left of the (unrotated) view rectangle.
|
||||
pub min: Pos2,
|
||||
pub origin: Point,
|
||||
pub zoom: f64,
|
||||
/// Quarter turns clockwise about `pivot` (the view centre).
|
||||
pub rot: u8,
|
||||
pub pivot: Pos2,
|
||||
}
|
||||
|
||||
/// Rotate `p` by `q` quarter turns clockwise about `c` (screen space, y down).
|
||||
fn quarter(p: Pos2, c: Pos2, q: u8) -> Pos2 {
|
||||
let (dx, dy) = (p.x - c.x, p.y - c.y);
|
||||
let (x, y) = match q % 4 {
|
||||
0 => (dx, dy),
|
||||
1 => (-dy, dx),
|
||||
2 => (-dx, -dy),
|
||||
_ => (dy, -dx),
|
||||
};
|
||||
pos2(c.x + x, c.y + y)
|
||||
}
|
||||
|
||||
/// The unrotated view rectangle for a screen rectangle and a rotation (width and height swap
|
||||
/// for quarter turns).
|
||||
pub fn view_rect(screen: Rect, rot: u8) -> Rect {
|
||||
if rot % 2 == 1 { Rect::from_center_size(screen.center(), vec2(screen.height(), screen.width())) } else { screen }
|
||||
}
|
||||
|
||||
impl Xf {
|
||||
pub fn new(rect: Rect, v: &View) -> Self {
|
||||
Xf { min: rect.min, origin: v.origin, zoom: v.zoom }
|
||||
Xf { min: rect.min, origin: v.origin, zoom: v.zoom, rot: v.rotation % 4, pivot: rect.center() }
|
||||
}
|
||||
pub fn to_screen(&self, p: Point) -> Pos2 {
|
||||
pos2(self.min.x + ((p.x - self.origin.x) * self.zoom) as f32, self.min.y + ((p.y - self.origin.y) * self.zoom) as f32)
|
||||
let s = pos2(self.min.x + ((p.x - self.origin.x) * self.zoom) as f32, self.min.y + ((p.y - self.origin.y) * self.zoom) as f32);
|
||||
if self.rot == 0 { s } else { quarter(s, self.pivot, self.rot) }
|
||||
}
|
||||
/// A screen position in the unrotated view.
|
||||
pub fn unrotate(&self, p: Pos2) -> Pos2 {
|
||||
if self.rot == 0 { p } else { quarter(p, self.pivot, 4 - self.rot) }
|
||||
}
|
||||
/// A screen-space movement in the unrotated view.
|
||||
pub fn unrotate_delta(&self, d: egui::Vec2) -> egui::Vec2 {
|
||||
self.unrotate(self.pivot + d) - self.pivot
|
||||
}
|
||||
pub fn to_canvas(&self, p: Pos2) -> Point {
|
||||
let p = self.unrotate(p);
|
||||
Point::new(self.origin.x + (p.x - self.min.x) as f64 / self.zoom, self.origin.y + (p.y - self.min.y) as f64 / self.zoom)
|
||||
}
|
||||
pub fn rect(&self, r: DRect) -> Rect {
|
||||
@@ -36,9 +68,12 @@ impl Xf {
|
||||
}
|
||||
/// Canvas → screen affine (for vello rendering: pixels = points × ppp).
|
||||
pub fn affine(&self, ppp: f64, area_min: Pos2) -> Affine {
|
||||
let dx = (self.min.x - area_min.x) as f64;
|
||||
let dy = (self.min.y - area_min.y) as f64;
|
||||
Affine::scale(ppp) * Affine::translate((dx, dy)) * Affine::scale(self.zoom) * Affine::translate((-self.origin.x, -self.origin.y))
|
||||
let unrotated = Affine::translate((self.min.x as f64, self.min.y as f64))
|
||||
* Affine::scale(self.zoom)
|
||||
* Affine::translate((-self.origin.x, -self.origin.y));
|
||||
let (cx, cy) = (self.pivot.x as f64, self.pivot.y as f64);
|
||||
let rot = Affine::translate((cx, cy)) * Affine::rotate(self.rot as f64 * std::f64::consts::FRAC_PI_2) * Affine::translate((-cx, -cy));
|
||||
Affine::scale(ppp) * Affine::translate((-(area_min.x as f64), -(area_min.y as f64))) * rot * unrotated
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +117,7 @@ pub fn fit(app: &mut DesignApp, rect: Rect, what: &str) {
|
||||
let zoom = ((rect.width() as f64 - 2.0 * pad) / b.width()).min((rect.height() as f64 - 2.0 * pad) / b.height()).clamp(0.05, 40.0);
|
||||
let origin = Point::new(b.center().x - rect.width() as f64 / 2.0 / zoom, b.center().y - rect.height() as f64 / 2.0 / zoom);
|
||||
if let Some(v) = app.view_mut() {
|
||||
*v = View { zoom, origin, fitted: true };
|
||||
*v = View { zoom, origin, fitted: true, rotation: v.rotation };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +168,7 @@ pub fn zoom_at(app: &mut DesignApp, screen: Pos2, factor: f64) {
|
||||
let Some(v) = app.view_mut() else { return };
|
||||
let xf = Xf::new(rect, v);
|
||||
let c = xf.to_canvas(screen);
|
||||
let screen = xf.unrotate(screen);
|
||||
let nz = (v.zoom * factor).clamp(0.05, 40.0);
|
||||
v.zoom = nz;
|
||||
v.origin = Point::new(c.x - (screen.x - rect.min.x) as f64 / nz, c.y - (screen.y - rect.min.y) as f64 / nz);
|
||||
@@ -196,8 +232,11 @@ fn mods(i: &egui::InputState, space: bool) -> Mods {
|
||||
pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let full = ui.available_rect_before_wrap();
|
||||
let rulers = app.ui.rulers && app.ui.screen_mode != ScreenMode::Presentation;
|
||||
let rect = if rulers { Rect::from_min_max(full.min + vec2(RULER, RULER), full.max) } else { full };
|
||||
let rot = app.view().map_or(0, |v| v.rotation % 4);
|
||||
// Rulers measure the unrotated layout, so they hide while the view is rotated.
|
||||
let rulers = app.ui.rulers && app.ui.screen_mode != ScreenMode::Presentation && rot == 0;
|
||||
let screen = if rulers { Rect::from_min_max(full.min + vec2(RULER, RULER), full.max) } else { full };
|
||||
let rect = view_rect(screen, rot);
|
||||
app.canvas_rect = Some(rect);
|
||||
let resp = ui.allocate_rect(full, Sense::click_and_drag());
|
||||
if !app.view().is_some_and(|v| v.fitted) {
|
||||
@@ -209,10 +248,10 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) {
|
||||
let xf = Xf::new(rect, &v);
|
||||
let doc = st.doc.clone();
|
||||
let layout = CanvasLayout::new(&doc, st.editing_parents);
|
||||
let painter = ui.painter_at(rect);
|
||||
let painter = ui.painter_at(screen);
|
||||
let preview = matches!(app.ui.screen_mode, ScreenMode::Preview | ScreenMode::Presentation);
|
||||
let bg = if app.ui.screen_mode == ScreenMode::Presentation { Color32::BLACK } else { t.pasteboard };
|
||||
painter.rect_filled(rect, 0.0, bg);
|
||||
painter.rect_filled(screen, 0.0, bg);
|
||||
// Page shadow: a hard 1.5 pt black offset on the right and bottom (InDesign 2026).
|
||||
for slot in &layout.slots {
|
||||
let r = xf.rect(slot.bounds);
|
||||
@@ -223,18 +262,28 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) {
|
||||
if let (Some(tex), Some(sh)) = (&app.canvas.texture, app.canvas.shown) {
|
||||
// Place the texture where its region is in the current view (shifted while panning,
|
||||
// scaled while a zoom re-render is pending).
|
||||
let k = (xf.zoom / sh.zoom) as f32;
|
||||
let tl = xf.to_screen(sh.origin);
|
||||
let tex_rect = Rect::from_min_size(tl, vec2(sh.size.0 * k, sh.size.1 * k));
|
||||
let uv = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
|
||||
// The texture's corners in canvas space, placed through the view (rotation included).
|
||||
let (w, h) = (sh.size.0 as f64 / sh.zoom, sh.size.1 as f64 / sh.zoom);
|
||||
let o = sh.origin;
|
||||
let corners = [
|
||||
(o, pos2(0.0, 0.0)),
|
||||
(Point::new(o.x + w, o.y), pos2(1.0, 0.0)),
|
||||
(Point::new(o.x + w, o.y + h), pos2(1.0, 1.0)),
|
||||
(Point::new(o.x, o.y + h), pos2(0.0, 1.0)),
|
||||
];
|
||||
let mut mesh = egui::Mesh::with_texture(tex.id());
|
||||
for (c, uv) in corners {
|
||||
mesh.vertices.push(egui::epaint::Vertex { pos: xf.to_screen(c), uv, color: Color32::WHITE });
|
||||
}
|
||||
mesh.indices.extend([0, 1, 2, 0, 2, 3]);
|
||||
if preview {
|
||||
// Preview: only page areas (trim) show content.
|
||||
for slot in &layout.slots {
|
||||
let r = xf.rect(slot.bounds);
|
||||
painter.with_clip_rect(r.intersect(rect)).image(tex.id(), tex_rect, uv, Color32::WHITE);
|
||||
painter.with_clip_rect(r.intersect(screen)).add(egui::Shape::mesh(mesh.clone()));
|
||||
}
|
||||
} else {
|
||||
painter.image(tex.id(), tex_rect, uv, Color32::WHITE);
|
||||
painter.add(egui::Shape::mesh(mesh));
|
||||
}
|
||||
}
|
||||
let hair = 1.0 / ui.ctx().pixels_per_point();
|
||||
@@ -268,12 +317,12 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
if let Some(r) = sel_rect
|
||||
&& !preview
|
||||
&& r.intersects(rect)
|
||||
&& r.intersects(screen)
|
||||
{
|
||||
crate::taskbar::show(app, &ui.ctx().clone(), r, rect);
|
||||
crate::taskbar::show(app, &ui.ctx().clone(), r, screen);
|
||||
}
|
||||
// Cursor.
|
||||
if let Some(p) = resp.hover_pos().filter(|p| rect.contains(*p)) {
|
||||
if let Some(p) = resp.hover_pos().filter(|p| screen.contains(*p)) {
|
||||
let space = ui.input(|i| i.key_down(egui::Key::Space)) && !app.session.wants_text();
|
||||
let c = if space { Cursor::Hand } else { app.session.cursor(xf.to_canvas(p), ui.input(|i| mods(i, false)), app.view_info()) };
|
||||
ui.ctx().set_cursor_icon(cursor_icon(c));
|
||||
@@ -1262,6 +1311,7 @@ fn handle_input(app: &mut DesignApp, ui: &mut egui::Ui, resp: &egui::Response, r
|
||||
} else if scroll != egui::Vec2::ZERO
|
||||
&& let Some(v) = app.view_mut()
|
||||
{
|
||||
let scroll = xf.unrotate_delta(scroll);
|
||||
v.origin = Point::new(v.origin.x - scroll.x as f64 / v.zoom, v.origin.y - scroll.y as f64 / v.zoom);
|
||||
}
|
||||
}
|
||||
@@ -1270,13 +1320,14 @@ fn handle_input(app: &mut DesignApp, ui: &mut egui::Ui, resp: &egui::Response, r
|
||||
// Distribute while resizing).
|
||||
let tool_drag: bool = ui.data(|d| d.get_temp(egui::Id::new(("canvas_pointer_down", app.pane)))).unwrap_or(false);
|
||||
if space && !tool_drag && resp.dragged() {
|
||||
let d = resp.drag_delta();
|
||||
let d = xf.unrotate_delta(resp.drag_delta());
|
||||
if let Some(v) = app.view_mut() {
|
||||
v.origin = Point::new(v.origin.x - d.x as f64 / v.zoom, v.origin.y - d.y as f64 / v.zoom);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if !space && guide_drag(app, ui, resp, rect, &xf) {
|
||||
// Ruler guides need the rulers (hidden while the view is rotated).
|
||||
if !space && xf.rot == 0 && guide_drag(app, ui, resp, rect, &xf) {
|
||||
return;
|
||||
}
|
||||
let m = ui.input(|i| mods(i, space));
|
||||
@@ -1296,7 +1347,7 @@ fn handle_input(app: &mut DesignApp, ui: &mut egui::Ui, resp: &egui::Response, r
|
||||
});
|
||||
if pressed
|
||||
&& !space
|
||||
&& let Some(o) = origin.filter(|o| rect.contains(*o) && resp.hovered())
|
||||
&& let Some(o) = origin.filter(|o| (if xf.rot == 0 { rect.contains(*o) } else { resp.rect.contains(*o) }) && resp.hovered())
|
||||
{
|
||||
events.push(PointerEvent { kind: if dbl { PointerKind::DoubleClick } else { PointerKind::Down }, pos: pos(o), mods: m });
|
||||
down = true;
|
||||
|
||||
@@ -232,11 +232,13 @@ pub struct View {
|
||||
pub zoom: f64,
|
||||
pub origin: Point,
|
||||
pub fitted: bool,
|
||||
/// View › Rotate Spread: quarter turns clockwise (0–3).
|
||||
pub rotation: u8,
|
||||
}
|
||||
|
||||
impl Default for View {
|
||||
fn default() -> Self {
|
||||
View { zoom: 0.5, origin: Point::new(-100.0, -100.0), fitted: false }
|
||||
View { zoom: 0.5, origin: Point::new(-100.0, -100.0), fitted: false, rotation: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ pub const UI_COMMANDS: &[(&str, &str, Option<&str>, &str)] = &[
|
||||
("window.hidePanelsExceptTools", "Show/Hide Panels Except Tools", Some("Shift+Tab"), "{}"),
|
||||
("window.nextDocument", "Next Document", Some("Cmd+F6"), "{}"),
|
||||
("window.previousDocument", "Previous Document", Some("Cmd+Shift+F6"), "{}"),
|
||||
("view.rotateSpread", "Rotate Spread", None, "{angle: 90 (clockwise) | -90 | 180 | 0 (clear)} — turn the view in quarter turns"),
|
||||
("view.proofColors", "Proof Colors", None, "{on?: bool} — simulate the proof target on screen"),
|
||||
(
|
||||
"view.proofSetup",
|
||||
@@ -333,6 +334,12 @@ pub const MENUS: &[(&str, &[&str])] = &[
|
||||
"-",
|
||||
"ui:view.hiddenCharacters",
|
||||
"ui:view.taggedFrames",
|
||||
">Rotate Spread",
|
||||
"ui:view.rotateSpread|90° CW|{\"angle\": 90}",
|
||||
"ui:view.rotateSpread|90° CCW|{\"angle\": -90}",
|
||||
"ui:view.rotateSpread|180°|{\"angle\": 180}",
|
||||
"ui:view.rotateSpread|Clear Rotation|{\"angle\": 0}",
|
||||
"<",
|
||||
">Proof Setup",
|
||||
"ui:view.proofSetup|Working CMYK|{\"target\": \"workingCmyk\"}",
|
||||
"ui:view.proofSetup|Internet Standard RGB (sRGB)|{\"target\": \"srgb\"}",
|
||||
@@ -1000,6 +1007,28 @@ pub fn run_ui(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value,
|
||||
}
|
||||
Ok(json!(app.session.active_index()))
|
||||
}
|
||||
"view.rotateSpread" => {
|
||||
let angle = p.get("angle").and_then(Value::as_i64).unwrap_or(90);
|
||||
let (Some(rect), Some(v)) = (app.canvas_rect, app.view().copied()) else { return Some(Err("no view".into())) };
|
||||
let xf = crate::canvas::Xf::new(rect, &v);
|
||||
let centre = xf.to_canvas(rect.center());
|
||||
let screen = crate::canvas::view_rect(rect, v.rotation);
|
||||
let rot = match angle {
|
||||
0 => 0,
|
||||
a => ((v.rotation as i64 + a.div_euclid(90)).rem_euclid(4)) as u8,
|
||||
};
|
||||
let new_rect = crate::canvas::view_rect(screen, rot);
|
||||
app.canvas_rect = Some(new_rect);
|
||||
if let Some(v) = app.view_mut() {
|
||||
v.rotation = rot;
|
||||
v.origin = designcraft_geom::Point::new(
|
||||
centre.x - new_rect.width() as f64 / 2.0 / v.zoom,
|
||||
centre.y - new_rect.height() as f64 / 2.0 / v.zoom,
|
||||
);
|
||||
}
|
||||
app.canvas.shown = None;
|
||||
Ok(json!(rot as u32 * 90))
|
||||
}
|
||||
"view.proofColors" => {
|
||||
app.ui.proof_colors = p.get("on").and_then(Value::as_bool).unwrap_or(!app.ui.proof_colors);
|
||||
app.canvas.shown = None;
|
||||
@@ -1513,6 +1542,10 @@ pub fn checked(app: &DesignApp, id: &str, params: &Value) -> Option<bool> {
|
||||
"window.controlBar" => app.ui.control_bar,
|
||||
"window.split" => app.split,
|
||||
"view.proofColors" => app.ui.proof_colors,
|
||||
"view.rotateSpread" => match params.get("angle").and_then(Value::as_i64) {
|
||||
Some(0) => app.view().is_some_and(|v| v.rotation == 0),
|
||||
_ => return None,
|
||||
},
|
||||
"view.proofSetup" => match (params.get("target").and_then(Value::as_str), params.get("simulatePaper")) {
|
||||
(Some(t), _) => app.ui.proof_setup.target.id() == t,
|
||||
(None, Some(_)) => app.ui.proof_setup.simulate_paper,
|
||||
@@ -1935,6 +1968,47 @@ mod tests {
|
||||
assert_eq!(app.session.active_index(), Some(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rotate_spread_view() {
|
||||
let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
app.session.execute("file.new", &json!({})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
let frame = |app: &mut crate::DesignApp| {
|
||||
let input =
|
||||
egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1400.0, 900.0))), ..Default::default() };
|
||||
let mut out = ctx.run_ui(input, |ui| {
|
||||
app.logic(&ui.ctx().clone());
|
||||
app.ui(ui);
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
};
|
||||
frame(&mut app);
|
||||
frame(&mut app);
|
||||
let r0 = app.canvas_rect.unwrap();
|
||||
let xf0 = crate::canvas::Xf::new(r0, app.view().unwrap());
|
||||
let centre = xf0.to_canvas(r0.center());
|
||||
assert_eq!(run_ui(&mut app, "view.rotateSpread", &json!({"angle": 90})).unwrap().unwrap(), json!(90));
|
||||
frame(&mut app);
|
||||
let r1 = app.canvas_rect.unwrap();
|
||||
// Width and height swap (the rulers hide while rotated, so it grows by their width).
|
||||
assert!(r0.width() > r0.height() && r1.height() > r1.width(), "{r0:?} {r1:?}");
|
||||
let xf = crate::canvas::Xf::new(r1, app.view().unwrap());
|
||||
let c = xf.to_canvas(r1.center());
|
||||
let tol = 30.0 / xf.zoom;
|
||||
assert!((c.x - centre.x).abs() < tol && (c.y - centre.y).abs() < tol, "the centre stays put");
|
||||
// Screen ↔ canvas round trip, and canvas +x points down the screen after a clockwise turn.
|
||||
let p = designcraft_geom::Point::new(c.x + 50.0, c.y);
|
||||
let sp = xf.to_screen(p);
|
||||
assert!(sp.y > r1.center().y + 1.0 && (sp.x - r1.center().x).abs() < 1.0, "{sp:?}");
|
||||
let back = xf.to_canvas(sp);
|
||||
assert!((back.x - p.x).abs() < 1e-3 && (back.y - p.y).abs() < 1e-3);
|
||||
run_ui(&mut app, "view.rotateSpread", &json!({"angle": -90})).unwrap().unwrap();
|
||||
assert_eq!(app.view().unwrap().rotation, 0);
|
||||
run_ui(&mut app, "view.rotateSpread", &json!({"angle": 180})).unwrap().unwrap();
|
||||
frame(&mut app);
|
||||
assert_eq!(run_ui(&mut app, "view.rotateSpread", &json!({"angle": 0})).unwrap().unwrap(), json!(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panels_float_and_dock() {
|
||||
let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
|
||||
+6
-6
@@ -11,12 +11,12 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
||||
|---|---|---|---|---|---|
|
||||
| P0 | 102 | 100 | 2 | 0 | 99% |
|
||||
| P1 | 97 | 94 | 3 | 0 | 98% |
|
||||
| P2 | 76 | 43 | 27 | 6 | 74% |
|
||||
| P2 | 76 | 43 | 30 | 3 | 76% |
|
||||
|
||||
| Area | Parity |
|
||||
|---|---|
|
||||
| Application shell & workspace | 96% |
|
||||
| Documents, pages, spreads | 96% |
|
||||
| Documents, pages, spreads | 98% |
|
||||
| Layers | 95% |
|
||||
| Frames, shapes & paths | 100% |
|
||||
| Transform | 100% |
|
||||
@@ -31,7 +31,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
||||
| Interactivity & digital | 90% |
|
||||
| Output & production | 80% |
|
||||
| XML & automation | 83% |
|
||||
| View & navigation | 93% |
|
||||
| View & navigation | 95% |
|
||||
| Undo, history, saving | 100% |
|
||||
| Accessibility | 90% |
|
||||
|
||||
@@ -66,7 +66,7 @@ Open P0 items: Vector/page formats (PDF/AI/SVG/EPS place), Export to PDF (print)
|
||||
| Documents, pages, spreads | Insert/move/duplicate/delete pages | P0 | D |
|
||||
| Documents, pages, spreads | Per-page size (Page tool) | P1 | D |
|
||||
| Documents, pages, spreads | Margins and Columns per page | P0 | D |
|
||||
| Documents, pages, spreads | Spread rotation (view) | P2 | M |
|
||||
| Documents, pages, spreads | Spread rotation (view) | P2 | P |
|
||||
| Documents, pages, spreads | Pasteboard | P0 | D |
|
||||
| Documents, pages, spreads | Sections & numbering | P0 | D |
|
||||
| Documents, pages, spreads | Page numbering display | P1 | D |
|
||||
@@ -75,7 +75,7 @@ Open P0 items: Vector/page formats (PDF/AI/SVG/EPS place), Export to PDF (print)
|
||||
| Documents, pages, spreads | Parent pages | P0 | D |
|
||||
| Documents, pages, spreads | Parent item overrides | P0 | D |
|
||||
| Documents, pages, spreads | Page transitions | P2 | D |
|
||||
| Documents, pages, spreads | Spread view rotation | P2 | M |
|
||||
| Documents, pages, spreads | Spread view rotation | P2 | P |
|
||||
| Documents, pages, spreads | Layout Adjustment | P2 | D |
|
||||
| Documents, pages, spreads | Adjust Layout | P2 | D |
|
||||
| Documents, pages, spreads | Liquid layout | P2 | D |
|
||||
@@ -307,7 +307,7 @@ Open P0 items: Vector/page formats (PDF/AI/SVG/EPS place), Export to PDF (print)
|
||||
| View & navigation | Display performance | P0 | D |
|
||||
| View & navigation | Navigate pages | P0 | D |
|
||||
| View & navigation | Split window | P2 | D |
|
||||
| View & navigation | Rotate spread view | P2 | M |
|
||||
| View & navigation | Rotate spread view | P2 | P |
|
||||
| View & navigation | Find/zoom to object | P1 | D |
|
||||
| Undo, history, saving | Unlimited undo/redo | P0 | D |
|
||||
| Undo, history, saving | Revert | P0 | D |
|
||||
|
||||
Reference in new issue
Block a user