mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-14 14:22:39 +00:00
Merge feat/alpha-channel-text-wrap from origin into feat/alpha-channel-text-wrap
This commit is contained in:
commit
7af2bd3b0b
5 files changed
+52
-57
No files matched your search
@@ -86,6 +86,15 @@ fn doc_signature(doc: &Document) -> u64 {
|
||||
(Arc::as_ptr(&doc.styles) as usize).hash(&mut h);
|
||||
format!("{:?}", doc.text_variables).hash(&mut h);
|
||||
sections_hash(doc).hash(&mut h);
|
||||
outlines_hash(doc).hash(&mut h);
|
||||
h.finish()
|
||||
}
|
||||
|
||||
/// The Alpha Channel outlines text wraps around: a relink traces a new one while the items and
|
||||
/// stories stay the same.
|
||||
fn outlines_hash(doc: &Document) -> u64 {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut h = std::collections::hash_map::DefaultHasher::new();
|
||||
for o in &doc.alpha_outlines {
|
||||
o.serial.hash(&mut h);
|
||||
}
|
||||
@@ -409,12 +418,9 @@ fn signature(doc: &Document, story: &Arc<Story>) -> (Vec<usize>, Vec<Arc<Item>>)
|
||||
sections_hash(doc) as usize,
|
||||
doc.font_scope as usize,
|
||||
usize::from(g.relative_to == designcraft_doc::GridRelative::TopMargin),
|
||||
outlines_hash(doc) as usize,
|
||||
];
|
||||
sig.extend([g.start, g.increment].into_iter().flat_map(bits));
|
||||
// Alpha Channel outlines: a relink traces one again without changing the story or the items
|
||||
// that wrap it.
|
||||
sig.extend(doc.alpha_outlines.iter().flat_map(|o| [(o.serial & 0xffff_ffff) as usize, (o.serial >> 32) as usize]));
|
||||
sig.push(usize::MAX);
|
||||
let mut keep = Vec::new();
|
||||
let mut spreads = Vec::new();
|
||||
for f in &story.frames {
|
||||
|
||||
@@ -302,19 +302,18 @@ pub struct TextWrap {
|
||||
/// transparent holes it encloses (off: holes are blocked with the shape around them).
|
||||
#[serde(skip_serializing_if = "std::ops::Not::not")]
|
||||
pub inside_edges: bool,
|
||||
/// Alpha Channel contour: pixels more opaque than this (0..254) count as the image. Saved only
|
||||
/// when it isn't the default.
|
||||
/// Alpha Channel contour: pixels more opaque than this (0..254) count as the image.
|
||||
#[serde(skip_serializing_if = "is_default_alpha_threshold")]
|
||||
pub alpha_threshold: u8,
|
||||
}
|
||||
|
||||
/// DesignCraft's default alpha threshold for Alpha Channel contour wraps.
|
||||
pub const DEFAULT_ALPHA_THRESHOLD: u8 = 25;
|
||||
|
||||
fn is_default_alpha_threshold(t: &u8) -> bool {
|
||||
*t == DEFAULT_ALPHA_THRESHOLD
|
||||
}
|
||||
|
||||
/// DesignCraft's default alpha threshold for Alpha Channel contour wraps.
|
||||
pub const DEFAULT_ALPHA_THRESHOLD: u8 = 25;
|
||||
|
||||
impl Default for TextWrap {
|
||||
fn default() -> Self {
|
||||
TextWrap {
|
||||
@@ -349,22 +348,3 @@ pub struct TextFrame {
|
||||
#[serde(default)]
|
||||
pub options: TextFrameOptions,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The alpha threshold is saved only when it isn't the default, so a wrap saved before it
|
||||
/// existed is saved the same way, and reads back as the default.
|
||||
#[test]
|
||||
fn the_default_alpha_threshold_is_not_saved() {
|
||||
let wrap = TextWrap { mode: WrapMode::Contour, ..Default::default() };
|
||||
let v = serde_json::to_value(&wrap).unwrap();
|
||||
assert!(v.get("alphaThreshold").is_none(), "{v}");
|
||||
assert_eq!(serde_json::from_value::<TextWrap>(v).unwrap(), wrap);
|
||||
let set = TextWrap { alpha_threshold: 200, ..wrap };
|
||||
let v = serde_json::to_value(&set).unwrap();
|
||||
assert_eq!(v["alphaThreshold"], 200);
|
||||
assert_eq!(serde_json::from_value::<TextWrap>(v).unwrap(), set);
|
||||
}
|
||||
}
|
||||
@@ -674,14 +674,13 @@ pub struct AlphaOutline {
|
||||
/// under the same id, and the outline is traced again. (Weak, so it doesn't keep old image
|
||||
/// data alive, and its address can't be reused by new data while it's held.)
|
||||
pub source: std::sync::Weak<Vec<u8>>,
|
||||
/// A number no other outline made in this process has ([`AlphaOutline::next_serial`]).
|
||||
/// Composition caches compare it: an outline traced again after a relink has a new one,
|
||||
/// while a dropped outline's address can be reused.
|
||||
/// A number no other outline traced in this process has ([`AlphaOutline::next_serial`]):
|
||||
/// composition caches compare it to tell a new outline from the one they used.
|
||||
pub serial: u64,
|
||||
}
|
||||
|
||||
impl AlphaOutline {
|
||||
/// A serial number not given out before in this process.
|
||||
/// A serial number for a new outline, unique within the process.
|
||||
pub fn next_serial() -> u64 {
|
||||
static NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
|
||||
NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
|
||||
|
||||
@@ -438,6 +438,40 @@ mod tests {
|
||||
assert_eq!((b.x0, b.x1), (24.0, 40.0), "the new image's outline");
|
||||
}
|
||||
|
||||
/// A wrap with the default threshold saves as it did before thresholds were stored; another
|
||||
/// threshold is saved.
|
||||
#[test]
|
||||
fn the_default_threshold_is_left_out_of_a_saved_wrap() {
|
||||
let v = serde_json::to_value(designcraft_doc::TextWrap::default()).unwrap();
|
||||
assert!(v.get("alphaThreshold").is_none(), "{v}");
|
||||
let back: designcraft_doc::TextWrap = serde_json::from_value(v).unwrap();
|
||||
assert_eq!(back.alpha_threshold, designcraft_doc::DEFAULT_ALPHA_THRESHOLD);
|
||||
let w = designcraft_doc::TextWrap { alpha_threshold: 200, ..Default::default() };
|
||||
assert_eq!(serde_json::to_value(w).unwrap()["alphaThreshold"], 200);
|
||||
}
|
||||
|
||||
/// A relink that changes only the image data recomposes the text around the new outline.
|
||||
#[test]
|
||||
fn text_is_composed_again_around_a_relinked_images_outline() {
|
||||
let (mut s, image, story) = page(&png(|x, _| x < 32), [180.0, 150.0, 220.0, 178.0]);
|
||||
s.execute("object.textWrap", &json!({"ids": [image], "contour": "alphaChannel"})).unwrap();
|
||||
assert!(!overset(&s, story), "the left half is visible: the caption on the right is set");
|
||||
let Some(designcraft_doc::Content::Graphic(g)) =
|
||||
s.doc().unwrap().doc.item(designcraft_doc::ItemId(image.as_u64().unwrap())).map(|it| it.content.clone())
|
||||
else {
|
||||
panic!("a graphic")
|
||||
};
|
||||
let right = Arc::new(png(|x, _| x >= 32));
|
||||
s.edit(|d, _| {
|
||||
let mut a = (**d.assets.get(&g.asset).unwrap()).clone();
|
||||
a.data = right.clone();
|
||||
d.assets.insert(g.asset, Arc::new(a));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
assert!(overset(&s, story), "the right half is visible now: it covers the caption");
|
||||
}
|
||||
|
||||
/// An image anchored in text keeps its outline: anchored objects wrap the lines after their
|
||||
/// anchor, and they are kept in the story, not on the spread.
|
||||
#[test]
|
||||
@@ -512,28 +546,4 @@ mod tests {
|
||||
assert!(s.execute("object.textWrap", &bad).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
/// Replacing the image data under the same asset, as a relink or link update does, lays the
|
||||
/// text out again around the new outline: neither the story nor the frames change.
|
||||
#[test]
|
||||
fn a_relinked_image_lays_the_text_out_again() {
|
||||
let (mut s, image, story) = page(&png(|x, _| (8..20).contains(&x) || (44..56).contains(&x)), [150.0, 120.0, 178.0, 170.0]);
|
||||
s.execute("object.textWrap", &json!({"ids": [image], "contour": "alphaChannel"})).unwrap();
|
||||
assert!(!overset(&s, story), "the gap between the legs stays open");
|
||||
let Some(designcraft_doc::Content::Graphic(g)) =
|
||||
s.doc().unwrap().doc.item(designcraft_doc::ItemId(image.as_u64().unwrap())).map(|it| it.content.clone())
|
||||
else {
|
||||
panic!("a graphic")
|
||||
};
|
||||
// The new image has no gap: one band across both legs.
|
||||
let solid = Arc::new(png(|x, _| (8..56).contains(&x)));
|
||||
s.edit(|d, _| {
|
||||
let mut a = (**d.assets.get(&g.asset).unwrap()).clone();
|
||||
a.data = solid.clone();
|
||||
d.assets.insert(g.asset, Arc::new(a));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
assert!(overset(&s, story), "the new image covers the caption");
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ Fidelity: **unmeasured** unless stated.
|
||||
| Paragraph rules, borders, shading | ✓ | ✓ | ✓ | |
|
||||
| Balance ragged lines | ✓ | ✓ | ✓ | |
|
||||
| Optical margin alignment | per story with a size, Ignore Optical Margin per paragraph and style | ✓ (hang table of our own, scaled by the story's size; Story panel; IDML `StoryPreference`, `IgnoreEdgeAlignment`) | ~ | hang amounts are ours, InDesign's unknown (#947) |
|
||||
| Text wrap | bounding box, shape, jump object/column, wrap sides (text on both sides of an object), contour options, on page items and anchored objects | ✓ | ~ | Alpha Channel contours follow the image's visible pixels within the frame (threshold, Include Inside Edges); Detect Edges and Photoshop Path contours follow the frame's path (composition doesn't read the image) |
|
||||
| Text wrap | bounding box, shape, jump object/column, wrap sides (text on both sides of an object), contour options, on page items and anchored objects | ✓ | ~ | Alpha Channel contours follow the outline of the image's visible pixels; Detect Edges and Photoshop Path contours follow the frame's path |
|
||||
| Anchored objects | inline, above line, custom | ✓ | ~ | anchored objects in table cells not drawn (#163) |
|
||||
| Footnotes, endnotes | ✓ | ✓ | ~ | footnotes don't split across columns yet |
|
||||
| Type on a path | ✓ | ✓ | ✓ | |
|
||||
|
||||
Reference in new issue
Block a user