Add a rotation to the text effect's placement
text_rotation turns the text and its box about the box's centre, so the centring and the offset still decide where it sits. Like the other placement params it stays on the stage and is not carried by a style. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <numbers>
|
||||||
|
|
||||||
#include "core/parallel.hpp"
|
#include "core/parallel.hpp"
|
||||||
#include "effects/source_fill.hpp"
|
#include "effects/source_fill.hpp"
|
||||||
@@ -57,6 +58,10 @@ const std::vector<ParamSpec>& TextEffect::params() const {
|
|||||||
"Moves the text right by this many pixels (left when negative)."),
|
"Moves the text right by this many pixels (left when negative)."),
|
||||||
ParamSpec::makeInt("text_offset_y", "Offset Y", 0, -20000, 20000,
|
ParamSpec::makeInt("text_offset_y", "Offset Y", 0, -20000, 20000,
|
||||||
"Moves the text down by this many pixels (up when negative)."),
|
"Moves the text down by this many pixels (up when negative)."),
|
||||||
|
ParamSpec::makeDouble("text_rotation", "Rotation", 0.0, -360.0, 360.0,
|
||||||
|
"Turns the text and its box about their centre by this many "
|
||||||
|
"degrees, clockwise (anticlockwise when negative).",
|
||||||
|
1.0, 1),
|
||||||
ParamSpec::makeDouble("text_opacity", "Text opacity", 1.0, 0.0, 1.0,
|
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(),
|
.styled(),
|
||||||
@@ -264,6 +269,18 @@ Image TextEffect::render(const std::vector<Image>& sources, int width, int heigh
|
|||||||
const double boxY = (centre ? (height - boxHeight) / 2.0 : 0.0) +
|
const double boxY = (centre ? (height - boxHeight) / 2.0 : 0.0) +
|
||||||
static_cast<double>(paramInt(values, "text_offset_y", 0));
|
static_cast<double>(paramInt(values, "text_offset_y", 0));
|
||||||
|
|
||||||
|
// The turn is about the middle of the box, so it never moves the text away
|
||||||
|
// from where the centring and the offset put it. Cairo's y points down,
|
||||||
|
// which makes a positive angle clockwise on screen.
|
||||||
|
const double rotation = paramDouble(values, "text_rotation", 0.0);
|
||||||
|
if (rotation != 0.0) {
|
||||||
|
const double cx = boxX + boxWidth / 2.0;
|
||||||
|
const double cy = boxY + boxHeight / 2.0;
|
||||||
|
cairo_translate(cr.get, cx, cy);
|
||||||
|
cairo_rotate(cr.get, rotation * std::numbers::pi / 180.0);
|
||||||
|
cairo_translate(cr.get, -cx, -cy);
|
||||||
|
}
|
||||||
|
|
||||||
if (drawsBox) {
|
if (drawsBox) {
|
||||||
cairo_save(cr.get);
|
cairo_save(cr.get);
|
||||||
cairo_rectangle(cr.get, boxX, boxY, boxWidth, boxHeight);
|
cairo_rectangle(cr.get, boxX, boxY, boxWidth, boxHeight);
|
||||||
|
|||||||
+16
-1
@@ -759,6 +759,20 @@ int main() {
|
|||||||
check(countRed(moved, 100, 60, 200, 120) > 0 && countRed(moved, 0, 0, 100, 60) == 0,
|
check(countRed(moved, 100, 60, 200, 120) > 0 && countRed(moved, 0, 0, 100, 60) == 0,
|
||||||
"the offset moves it down and to the right");
|
"the offset moves it down and to the right");
|
||||||
|
|
||||||
|
// A wide line turned a quarter stands upright: it reaches above and
|
||||||
|
// below the band it filled unturned, and no longer out to the sides.
|
||||||
|
ParamValues wide = plain;
|
||||||
|
wide["text"] = std::string("HHHHHH");
|
||||||
|
wide["font_size"] = 40.0;
|
||||||
|
const auto flat = renderText(blue, 200, 120, wide);
|
||||||
|
ParamValues turned = wide;
|
||||||
|
turned["text_rotation"] = 90.0;
|
||||||
|
const auto upright = renderText(blue, 200, 120, turned);
|
||||||
|
check(countRed(flat, 0, 0, 200, 25) == 0 && countRed(upright, 0, 0, 200, 25) > 0,
|
||||||
|
"rotation turns the text about its centre");
|
||||||
|
check(countRed(flat, 0, 0, 50, 120) > 0 && countRed(upright, 0, 0, 50, 120) == 0,
|
||||||
|
"and takes it in from the sides");
|
||||||
|
|
||||||
// Weight and size are the two knobs the glyphs themselves answer to: a
|
// Weight and size are the two knobs the glyphs themselves answer to: a
|
||||||
// bolder or a larger setting covers strictly more of the canvas.
|
// bolder or a larger setting covers strictly more of the canvas.
|
||||||
ParamValues bold = plain;
|
ParamValues bold = plain;
|
||||||
@@ -877,7 +891,8 @@ int main() {
|
|||||||
carries("box_padding") && carries("box_opacity"),
|
carries("box_padding") && carries("box_opacity"),
|
||||||
"the look is what it carries");
|
"the look is what it carries");
|
||||||
check(!carries("text") && !carries("text_wrap") && !carries("box_image") &&
|
check(!carries("text") && !carries("text_wrap") && !carries("box_image") &&
|
||||||
!carries("text_center") && !carries("text_offset_x") && !carries("text_offset_y"),
|
!carries("text_center") && !carries("text_offset_x") && !carries("text_offset_y") &&
|
||||||
|
!carries("text_rotation"),
|
||||||
"the words and the placement stay on the stage");
|
"the words and the placement stay on the stage");
|
||||||
check(styleSpecs("").empty() && styleSpecs("nosuchkind").empty(),
|
check(styleSpecs("").empty() && styleSpecs("nosuchkind").empty(),
|
||||||
"a kind no effect claims carries nothing");
|
"a kind no effect claims carries nothing");
|
||||||
|
|||||||
Reference in New Issue
Block a user