mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-25 10:46:50 -06:00
A character attribute strokeBehindFill paints a text stroke under the fill at double width, so its full weight lies outside the letters and they keep their shape. Screen and PNG rendering, PDF export (stroke as outlines first, then the filled text, so the text is not duplicated; clipped to the outside of the glyphs when there is no fill), Create Outlines (outside- aligned stroke), the Properties panel and Character Style Options. IDML StrokeAlignment on CharacterStyleRange, CharacterStyle and ParagraphStyle imports and exports as the attribute (OutsideAlignment = on, CenterAlignment = off).
64 lines
3.2 KiB
Rust
64 lines
3.2 KiB
Rust
//! Stroke behind fill on text: the fill covers the inner half of a double-width stroke.
|
|
use designcraft_color::{Color, swatch::Swatch};
|
|
use designcraft_compose::Cache;
|
|
use designcraft_doc::build::NewDocument;
|
|
use designcraft_doc::{Document, ParaFormat, SpreadRef};
|
|
use designcraft_geom::Rect;
|
|
use designcraft_render::{RenderOptions, Rendered, Renderer};
|
|
|
|
const WHITE: [u8; 4] = [255, 255, 255, 255];
|
|
const RED: [u8; 4] = [255, 0, 0, 255];
|
|
const BLUE: [u8; 4] = [0, 0, 255, 255];
|
|
const WEIGHT: f64 = 12.0;
|
|
|
|
/// A 200 pt "I" filled with `fill`, stroked red at [`WEIGHT`] (none when `stroke` is false).
|
|
fn render(fill: &str, stroke: bool, behind: bool) -> Rendered {
|
|
let mut d = Document::new(&NewDocument { width: 400.0, height: 400.0, facing_pages: false, ..Default::default() });
|
|
d.swatches.push(Swatch::color("Red", Color::rgb8(255, 0, 0)));
|
|
d.swatches.push(Swatch::color("Blue", Color::rgb8(0, 0, 255)));
|
|
let lid = d.default_layer();
|
|
let (_, sid) = d.add_text_frame(SpreadRef::Doc(0), Rect::new(40.0, 0.0, 400.0, 400.0), lid, "I", ParaFormat::default()).unwrap();
|
|
d.story_mut(sid).unwrap().format_chars(0..1, |f| {
|
|
f.over.size = Some(200.0);
|
|
f.over.fill = Some(fill.into());
|
|
f.over.stroke = Some(if stroke { "Red" } else { designcraft_color::swatch::NONE }.into());
|
|
f.over.stroke_weight = Some(WEIGHT);
|
|
f.over.stroke_behind_fill = Some(behind);
|
|
});
|
|
let mut r = Renderer::new();
|
|
r.threads = 0;
|
|
let cache = Cache::new();
|
|
let cs = cache.get(&d, sid, None);
|
|
assert!(!cs.frames[0].lines.is_empty(), "the glyph is set");
|
|
r.render_page(&d, &cache, 0, 1.0, false, &RenderOptions { paper: false, background: Some(WHITE), ..Default::default() }).unwrap()
|
|
}
|
|
|
|
/// The left edge of the stem on a row through the middle of the unstroked glyph.
|
|
fn stem_edge() -> (u32, u32) {
|
|
let img = render("Blue", false, false);
|
|
let blue: Vec<(u32, u32)> = (0..img.height).flat_map(|y| (0..img.width).map(move |x| (x, y))).filter(|&(x, y)| img.pixel(x, y) == BLUE).collect();
|
|
assert!(!blue.is_empty(), "the glyph is drawn");
|
|
let y0 = blue.iter().map(|p| p.1).min().unwrap();
|
|
let y1 = blue.iter().map(|p| p.1).max().unwrap();
|
|
let y = (y0 + y1) / 2;
|
|
let x = (0..img.width).find(|&x| img.pixel(x, y) == BLUE).unwrap();
|
|
(x, y)
|
|
}
|
|
|
|
#[test]
|
|
fn stroke_behind_fill_shows_only_outside_the_letter() {
|
|
let (x, y) = stem_edge();
|
|
let inner = x + 3; // inside the outline, within the inner half of a centred stroke
|
|
let outer = x - 9; // outside the outline, beyond the outer half of a centred stroke
|
|
let over = render("Blue", true, false);
|
|
assert_eq!(over.pixel(inner, y), RED, "a centred stroke covers the fill's edge");
|
|
assert_eq!(over.pixel(outer, y), WHITE, "a centred stroke reaches half its weight outside");
|
|
let behind = render("Blue", true, true);
|
|
assert_eq!(behind.pixel(inner, y), BLUE, "the fill covers the stroke behind it");
|
|
assert_eq!(behind.pixel(outer, y), RED, "the full stroke weight lies outside the letter");
|
|
assert_eq!(behind.pixel(x - 15, y), WHITE, "and no further");
|
|
let hollow = render(designcraft_color::swatch::NONE, true, true);
|
|
assert_eq!(hollow.pixel(inner, y), WHITE, "with no fill the letter stays empty");
|
|
assert_eq!(hollow.pixel(outer, y), RED);
|
|
}
|