Let several text stages share one named look

A picture with a title, a caption and a credit meant typing the same
font, size and colour into every text stage, and changing the look meant
typing them all again. A project now holds named styles: a stage follows
one, and editing it moves every stage that follows it at once.

The front-ends learn nothing effect-specific. A ParamSpec can be marked
.styled(), saying a style carries it rather than the stage, and
Effect::styleKind() names the pool it comes from -- so styleSpecs(kind)
is to a style what params() is to a stage, and a second text-ish effect
would slot into the styles that already exist. The look is what a style
carries; the words and the placement stay on the stage, which is what
lets two stages share a typography in two different corners.

stageValues() resolves one in a single place, and a stage keeps a full
set of values of its own beside the style, so switching it to Custom,
dropping the style, or opening the project in a build that does not know
it renders exactly the same picture. A style moves stages together; it
never holds the only copy of anything.

The command line builds no styles and passes none, so it renders what it
always did.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Si
2026-09-19 21:55:55 +02:00
co-authored by Claude Opus 5
parent eb417bb41a
commit 3aade6f361
17 changed files with 890 additions and 131 deletions
+20 -11
View File
@@ -17,31 +17,39 @@ const std::vector<ParamSpec>& TextEffect::params() const {
"What to write; a line break starts a new line."),
ParamSpec::makeText("font_family", "Font", "Sans",
"Font family, as it is named on this machine: Sans, Serif, Monospace, "
"\"DejaVu Serif\", ..."),
"\"DejaVu Serif\", ...")
.styled(),
ParamSpec::makeChoice("font_weight", "Weight", "normal",
{"thin", "light", "normal", "medium", "semibold", "bold", "heavy"},
"How bold the glyphs are; a family without the chosen weight is "
"rendered in the nearest one it has."),
ParamSpec::makeBool("font_italic", "Italic", false, "Slants the glyphs."),
"rendered in the nearest one it has.")
.styled(),
ParamSpec::makeBool("font_italic", "Italic", false, "Slants the glyphs.").styled(),
ParamSpec::makeDouble("font_size", "Size", 96.0, 4.0, 2000.0,
"Height of the glyphs in pixels.", 1.0, 1),
ParamSpec::makeColor("text_color", "Colour", "#ffffff", "The colour of the glyphs."),
"Height of the glyphs in pixels.", 1.0, 1)
.styled(),
ParamSpec::makeColor("text_color", "Colour", "#ffffff", "The colour of the glyphs.").styled(),
ParamSpec::makeChoice("text_align", "Alignment", "center", {"left", "center", "right"},
"How several lines line up against each other."),
"How several lines line up against each other.")
.styled(),
ParamSpec::makeInt("text_wrap", "Wrap width", 0, 0, 20000,
"Breaks the text into lines no wider than this many pixels; 0 writes "
"it as given."),
ParamSpec::makeChoice("text_box", "Box", "none", {"none", "colour", "image"},
"What the text sits on: nothing, a filled rectangle, or a picture "
"from disk cropped to the rectangle."),
"from disk cropped to the rectangle.")
.styled(),
ParamSpec::makeColor("box_color", "Box colour", "#000000",
"The colour the box is filled with."),
"The colour the box is filled with.")
.styled(),
ParamSpec::makePath("box_image", "Box picture",
"The picture the box is filled with, scaled to cover it."),
ParamSpec::makeInt("box_padding", "Box padding", 24, 0, 2000,
"How far the box reaches past the text on every side."),
"How far the box reaches past the text on every side.")
.styled(),
ParamSpec::makeDouble("box_opacity", "Box opacity", 1.0, 0.0, 1.0,
"How strongly the box is mixed over the input."),
"How strongly the box is mixed over the input.")
.styled(),
ParamSpec::makeBool("text_center", "Centre", true,
"Places the text in the middle of the canvas rather than at its top "
"left corner; the offset is counted from there either way."),
@@ -50,7 +58,8 @@ const std::vector<ParamSpec>& TextEffect::params() const {
ParamSpec::makeInt("text_offset_y", "Offset Y", 0, -20000, 20000,
"Moves the text down by this many pixels (up when negative)."),
ParamSpec::makeDouble("text_opacity", "Text opacity", 1.0, 0.0, 1.0,
"How strongly the glyphs are mixed over what is behind them."),
"How strongly the glyphs are mixed over what is behind them.")
.styled(),
};
return specs;
}
+1
View File
@@ -28,6 +28,7 @@ public:
"box filled with a colour or with a picture.";
}
const std::vector<ParamSpec>& params() const override;
std::string styleKind() const override { return "text"; }
bool needsSources() const override { return false; }
Image render(const std::vector<Image>& sources, int width, int height, Rng& rng,