Blender V4.5
node_geo_string_to_curves.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "DNA_curve_types.h"
6
7#include "BKE_curve.hh"
9#include "BKE_curves.hh"
10#include "BKE_instances.hh"
11#include "BKE_vfont.hh"
12
13#include "BLI_bounds.hh"
14#include "BLI_math_matrix.hh"
15#include "BLI_string_utf8.h"
16#include "BLI_task.hh"
17
18#include "UI_interface.hh"
19#include "UI_resources.hh"
20
21#include "GEO_randomize.hh"
22
23#include "node_geometry_util.hh"
24
26
28
30{
31 b.add_input<decl::String>("String").hide_label();
32 b.add_input<decl::Float>("Size").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE);
33 b.add_input<decl::Float>("Character Spacing").default_value(1.0f).min(0.0f);
34 b.add_input<decl::Float>("Word Spacing").default_value(1.0f).min(0.0f);
35 b.add_input<decl::Float>("Line Spacing").default_value(1.0f).min(0.0f);
36 b.add_input<decl::Float>("Text Box Width").default_value(0.0f).min(0.0f).subtype(PROP_DISTANCE);
37 auto &height = b.add_input<decl::Float>("Text Box Height")
38 .default_value(0.0f)
39 .min(0.0f)
40 .subtype(PROP_DISTANCE)
41 .make_available([](bNode &node) {
42 node_storage(node).overflow = GEO_NODE_STRING_TO_CURVES_MODE_SCALE_TO_FIT;
43 });
44 b.add_output<decl::Geometry>("Curve Instances");
45 auto &remainder = b.add_output<decl::String>("Remainder").make_available([](bNode &node) {
46 node_storage(node).overflow = GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE;
47 });
48 b.add_output<decl::Int>("Line").field_on_all().translation_context(BLT_I18NCONTEXT_ID_TEXT);
49 b.add_output<decl::Vector>("Pivot Point").field_on_all();
50
51 const bNode *node = b.node_or_null();
52 if (node != nullptr) {
53 const NodeGeometryStringToCurves &storage = node_storage(*node);
55 storage.overflow);
56
57 remainder.available(overflow == GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE);
58 height.available(overflow != GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW);
59 }
60}
61
62static void node_layout(uiLayout *layout, bContext *C, PointerRNA *ptr)
63{
64 uiLayoutSetPropSep(layout, true);
65 uiLayoutSetPropDecorate(layout, false);
66 uiTemplateID(layout, C, ptr, "font", nullptr, "FONT_OT_open", "FONT_OT_unlink");
67 layout->prop(ptr, "overflow", UI_ITEM_NONE, "", ICON_NONE);
68 layout->prop(ptr, "align_x", UI_ITEM_NONE, "", ICON_NONE);
69 layout->prop(ptr, "align_y", UI_ITEM_NONE, "", ICON_NONE);
70 layout->prop(ptr, "pivot_mode", UI_ITEM_NONE, IFACE_("Pivot Point"), ICON_NONE);
71}
72
84
86{
87 const NodeGeometryStringToCurves &storage = node_storage(params.node());
89 storage.pivot_mode;
90
91 const std::optional<Bounds<float3>> bounds = bounds::min_max(curves.positions());
92
93 /* Check if curve is empty. */
94 if (!bounds.has_value()) {
95 return {0.0f, 0.0f, 0.0f};
96 }
97 const float3 min = bounds->min;
98 const float3 max = bounds->max;
99
100 switch (pivot_mode) {
102 return (min + max) / 2;
104 return float3(min.x, min.y, 0.0f);
106 return float3((min.x + max.x) / 2, min.y, 0.0f);
108 return float3(max.x, min.y, 0.0f);
110 return float3(min.x, max.y, 0.0f);
112 return float3((min.x + max.x) / 2, max.y, 0.0f);
114 return float3(max.x, max.y, 0.0f);
115 }
116 return {0.0f, 0.0f, 0.0f};
117}
118
120 /* Position of each character. */
122
123 /* Line number of each character. */
125
126 /* Map of Pivot point for each character code. */
128
129 /* UTF32 Character codes. */
131
132 /* The text that fit into the text box, with newline character sequences replaced. */
133 std::string text;
134
135 /* The text that didn't fit into the text box in "Truncate" mode. May be empty. */
136 std::string truncated_text;
137
138 /* Font size could be modified if in "Scale to fit"-mode. */
140};
141
142static std::optional<TextLayout> get_text_layout(GeoNodeExecParams &params)
143{
144 VFont *vfont = reinterpret_cast<VFont *>(params.node().id);
145 if (!vfont) {
146 params.error_message_add(NodeWarningType::Error, TIP_("Font not specified"));
147 return std::nullopt;
148 }
149
150 TextLayout layout;
151 layout.text = params.extract_input<std::string>("String");
152 if (layout.text.empty()) {
153 return std::nullopt;
154 }
155
156 const NodeGeometryStringToCurves &storage = node_storage(params.node());
158 storage.overflow;
160 storage.align_x;
162 storage.align_y;
163
164 const float font_size = std::max(params.extract_input<float>("Size"), 0.0f);
165 const float char_spacing = params.extract_input<float>("Character Spacing");
166 const float word_spacing = params.extract_input<float>("Word Spacing");
167 const float line_spacing = params.extract_input<float>("Line Spacing");
168 const float textbox_w = params.extract_input<float>("Text Box Width");
169 const float textbox_h = overflow == GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW ?
170 0.0f :
171 params.extract_input<float>("Text Box Height");
172
173 Curve cu = dna::shallow_zero_initialize();
174 cu.ob_type = OB_FONT;
175 /* Set defaults */
176 cu.resolu = 12;
177 cu.smallcaps_scale = 0.75f;
178 cu.wordspace = 1.0f;
179 /* Set values from inputs */
180 cu.spacemode = align_x;
181 cu.align_y = align_y;
182 cu.fsize = font_size;
183 cu.spacing = char_spacing;
184 cu.wordspace = word_spacing;
185 cu.linedist = line_spacing;
186 cu.vfont = vfont;
187 cu.overflow = overflow;
188 cu.tb = MEM_calloc_arrayN<TextBox>(MAXTEXTBOX, __func__);
189 cu.tb->w = textbox_w;
190 cu.tb->h = textbox_h;
191 cu.totbox = 1;
192 size_t len_bytes;
193 size_t len_chars = BLI_strlen_utf8_ex(layout.text.c_str(), &len_bytes);
194 cu.len_char32 = len_chars;
195 cu.len = len_bytes;
196 cu.pos = len_chars;
197 /* The reason for the additional character here is unknown, but reflects other code elsewhere. */
198 cu.str = MEM_malloc_arrayN<char>(len_bytes + sizeof(char32_t), __func__);
199 memcpy(cu.str, layout.text.c_str(), len_bytes + 1);
200 cu.strinfo = MEM_calloc_arrayN<CharInfo>(len_chars + 1, __func__);
201
202 CharTrans *chartransdata = nullptr;
203 int text_len;
204 bool text_free;
205 const char32_t *r_text = nullptr;
206 float final_font_size = 0.0f;
207 /* Mode FO_DUPLI used because it doesn't create curve splines. */
208 BKE_vfont_to_curve_ex(nullptr,
209 &cu,
210 FO_DUPLI,
211 nullptr,
212 &r_text,
213 &text_len,
214 &text_free,
215 &chartransdata,
216 &final_font_size);
217
218 if (text_free) {
219 MEM_freeN(r_text);
220 }
221
222 Span<CharInfo> info{cu.strinfo, text_len};
223 layout.final_font_size = final_font_size;
224 layout.positions.reserve(text_len);
225
226 for (const int i : IndexRange(text_len)) {
227 CharTrans &ct = chartransdata[i];
228 layout.positions.append(float2(ct.xof, ct.yof) * layout.final_font_size);
229
230 if (ct.is_overflow && (cu.overflow == CU_OVERFLOW_TRUNCATE)) {
231 const int offset = BLI_str_utf8_offset_from_index(
232 layout.text.c_str(), layout.text.size(), i + 1);
233 layout.truncated_text = layout.text.substr(offset);
234 layout.text = layout.text.substr(0, offset);
235 break;
236 }
237 }
238
239 if (params.anonymous_attribute_output_is_required("Line")) {
240 layout.line_numbers.reinitialize(layout.positions.size());
241 for (const int i : layout.positions.index_range()) {
242 CharTrans &ct = chartransdata[i];
243 layout.line_numbers[i] = ct.linenr;
244 }
245 }
246
247 /* Convert UTF8 encoded string to UTF32. */
248 len_chars = BLI_strlen_utf8_ex(layout.text.c_str(), &len_bytes);
249 layout.char_codes.resize(len_chars + 1);
250 BLI_str_utf8_as_utf32(layout.char_codes.data(), layout.text.c_str(), layout.char_codes.size());
251 layout.char_codes.remove_last();
252
253 MEM_SAFE_FREE(chartransdata);
254 MEM_SAFE_FREE(cu.str);
255 MEM_SAFE_FREE(cu.strinfo);
256 MEM_SAFE_FREE(cu.tb);
257
258 return layout;
259}
260
263 TextLayout &layout,
264 bke::Instances &instances)
265{
266 VFont *vfont = reinterpret_cast<VFont *>(params.node().id);
268 bool pivot_required = params.anonymous_attribute_output_is_required("Pivot Point");
269
270 for (int i : layout.char_codes.index_range()) {
271 if (handles.contains(layout.char_codes[i])) {
272 continue;
273 }
274 Curve cu = dna::shallow_zero_initialize();
275 cu.ob_type = OB_FONT;
276 cu.resolu = 12;
277 cu.vfont = vfont;
278 CharInfo charinfo = {0};
279 charinfo.mat_nr = 1;
280
281 BKE_vfont_char_build(&cu, &cu.nurb, layout.char_codes[i], &charinfo, false, 0, 0, 0, i, 1);
282 Curves *curves_id = bke::curve_legacy_to_curves(cu);
283 if (curves_id == nullptr) {
284 if (pivot_required) {
285 layout.pivot_points.add_new(layout.char_codes[i], float3(0));
286 }
287 handles.add_new(layout.char_codes[i], instances.add_reference({}));
288 continue;
289 }
290
291 bke::CurvesGeometry &curves = curves_id->geometry.wrap();
293
295
297 curves.transform(size_matrix);
298
299 if (pivot_required) {
300 float3 pivot_point = get_pivot_point(params, curves);
301 layout.pivot_points.add_new(layout.char_codes[i], pivot_point);
302 }
303
304 GeometrySet geometry_set = GeometrySet::from_curves(curves_id);
305
306 {
307 const char32_t char_code[2] = {layout.char_codes[i], 0};
308 char inserted_utf8[8] = {0};
309 const size_t len = BLI_str_utf32_as_utf8(inserted_utf8, char_code, sizeof(inserted_utf8));
310 geometry_set.name = std::string(inserted_utf8, len);
311 }
312
313 handles.add_new(layout.char_codes[i], instances.add_reference(std::move(geometry_set)));
314 }
315 return handles;
316}
317
319 const Map<int, int> &char_handles,
320 const TextLayout &layout)
321{
322 instances.resize(layout.positions.size());
324 MutableSpan<float4x4> transforms = instances.transforms_for_write();
325
326 threading::parallel_for(IndexRange(layout.positions.size()), 256, [&](IndexRange range) {
327 for (const int i : range) {
328 handles[i] = char_handles.lookup(layout.char_codes[i]);
329 transforms[i] = math::from_location<float4x4>(
330 {layout.positions[i].x, layout.positions[i].y, 0});
331 }
332 });
333}
334
336 const TextLayout &layout,
337 bke::Instances &instances)
338{
339 MutableAttributeAccessor attributes = instances.attributes_for_write();
340
341 if (std::optional<std::string> line_id = params.get_output_anonymous_attribute_id_if_needed(
342 "Line"))
343 {
344 SpanAttributeWriter<int> line_attribute = attributes.lookup_or_add_for_write_only_span<int>(
345 *line_id, AttrDomain::Instance);
346 line_attribute.span.copy_from(layout.line_numbers);
347 line_attribute.finish();
348 }
349
350 if (std::optional<std::string> pivot_id = params.get_output_anonymous_attribute_id_if_needed(
351 "Pivot Point"))
352 {
353 SpanAttributeWriter<float3> pivot_attribute =
354 attributes.lookup_or_add_for_write_only_span<float3>(*pivot_id, AttrDomain::Instance);
355
356 for (const int i : layout.char_codes.index_range()) {
357 pivot_attribute.span[i] = layout.pivot_points.lookup(layout.char_codes[i]);
358 }
359
360 pivot_attribute.finish();
361 }
362}
363
365{
366 std::optional<TextLayout> layout = get_text_layout(params);
367 if (!layout) {
368 params.set_default_remaining_outputs();
369 return;
370 }
371
372 const NodeGeometryStringToCurves &storage =
373 *(const NodeGeometryStringToCurves *)params.node().storage;
375 params.set_output("Remainder", std::move(layout->truncated_text));
376 }
377
378 if (layout->positions.is_empty()) {
379 params.set_output("Curve Instances", GeometrySet());
380 params.set_default_remaining_outputs();
381 return;
382 }
383
384 /* Create and add instances. */
385 std::unique_ptr<bke::Instances> instances = std::make_unique<bke::Instances>();
386 Map<int, int> char_handles = create_curve_instances(params, *layout, *instances);
387 add_instances_from_handles(*instances, char_handles, *layout);
388 create_attributes(params, *layout, *instances);
389
390 params.set_output("Curve Instances", GeometrySet::from_instances(instances.release()));
391}
392
393static void node_register()
394{
395 static blender::bke::bNodeType ntype;
396
397 geo_node_type_base(&ntype, "GeometryNodeStringToCurves", GEO_NODE_STRING_TO_CURVES);
398 ntype.ui_name = "String to Curves";
399 ntype.ui_description =
400 "Generate a paragraph of text with a specific font, using a curve instance to store each "
401 "character";
402 ntype.enum_name_legacy = "STRING_TO_CURVES";
404 ntype.declare = node_declare;
406 ntype.initfunc = node_init;
407 blender::bke::node_type_size(ntype, 190, 120, 700);
409 ntype, "NodeGeometryStringToCurves", node_free_standard_storage, node_copy_standard_storage);
412}
413NOD_REGISTER_NODE(node_register)
414
415} // namespace blender::nodes::node_geo_string_to_curves_cc
void BKE_nurbList_free(ListBase *lb)
Definition curve.cc:601
Low-level operations for curves.
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1215
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:447
#define GEO_NODE_STRING_TO_CURVES
bool BKE_vfont_to_curve_ex(Object *ob, Curve *cu, eEditFontMode mode, ListBase *r_nubase, const char32_t **r_text, int *r_text_len, bool *r_text_free, CharTrans **r_chartransdata, float *r_font_size_eval)
@ FO_DUPLI
Definition BKE_vfont.hh:81
void BKE_vfont_char_build(Curve *cu, ListBase *nubase, unsigned int charcode, const CharInfo *info, bool is_smallcaps, float ofsx, float ofsy, float rot, int charidx, float fsize)
VFont * BKE_vfont_builtin_ensure()
Definition vfont.cc:382
int BLI_str_utf8_offset_from_index(const char *str, size_t str_len, int index_target) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
size_t size_t BLI_str_utf32_as_utf8(char *__restrict dst, const char32_t *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
size_t BLI_str_utf8_as_utf32(char32_t *__restrict dst_w, const char *__restrict src_c, size_t dst_w_maxncpy) ATTR_NONNULL(1
size_t BLI_strlen_utf8_ex(const char *strc, size_t *r_len_bytes) ATTR_NONNULL(1
#define BLT_I18NCONTEXT_ID_TEXT
#define TIP_(msgid)
#define IFACE_(msgid)
#define MAXTEXTBOX
@ CU_OVERFLOW_TRUNCATE
GeometryNodeStringToCurvesAlignXMode
@ GEO_NODE_STRING_TO_CURVES_ALIGN_X_LEFT
GeometryNodeStringToCurvesPivotMode
@ GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_MIDPOINT
@ GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_BOTTOM_RIGHT
@ GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_BOTTOM_CENTER
@ GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_TOP_LEFT
@ GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_TOP_CENTER
@ GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_TOP_RIGHT
@ GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_BOTTOM_LEFT
GeometryNodeStringToCurvesAlignYMode
@ GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP_BASELINE
GeometryNodeStringToCurvesOverflowMode
@ GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE
@ GEO_NODE_STRING_TO_CURVES_MODE_SCALE_TO_FIT
@ GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW
@ OB_FONT
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ PROP_DISTANCE
Definition RNA_types.hh:244
#define C
Definition RandGen.cpp:29
void uiTemplateID(uiLayout *layout, const bContext *C, PointerRNA *ptr, blender::StringRefNull propname, const char *newop, const char *openop, const char *unlinkop, int filter=UI_TEMPLATE_ID_FILTER_ALL, bool live_icon=false, std::optional< blender::StringRef > text=std::nullopt)
void uiLayoutSetPropSep(uiLayout *layout, bool is_sep)
#define UI_ITEM_NONE
void uiLayoutSetPropDecorate(uiLayout *layout, bool is_sep)
BMesh const char void * data
void reinitialize(const int64_t new_size)
Definition BLI_array.hh:398
int64_t size() const
IndexRange index_range() const
void resize(const int64_t new_size)
T * data()
void remove_last()
Span< float3 > positions() const
void transform(const float4x4 &matrix)
MutableSpan< int > reference_handles_for_write()
Definition instances.cc:222
int add_reference(const InstanceReference &reference)
Definition instances.cc:271
void resize(int capacity)
Definition instances.cc:197
bke::MutableAttributeAccessor attributes_for_write()
Definition instances.cc:68
MutableSpan< float4x4 > transforms_for_write()
Definition instances.cc:240
GSpanAttributeWriter lookup_or_add_for_write_only_span(StringRef attribute_id, AttrDomain domain, eCustomDataType data_type)
void make_available(bNode &node) const
#define MEM_SAFE_FREE(v)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
Curves * curve_legacy_to_curves(const Curve &curve_legacy)
void node_type_size(bNodeType &ntype, int width, int minwidth, int maxwidth)
Definition node.cc:5573
void node_register_type(bNodeType &ntype)
Definition node.cc:2748
void node_type_storage(bNodeType &ntype, std::optional< StringRefNull > storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:5603
std::optional< Bounds< T > > min_max(const std::optional< Bounds< T > > &a, const T &b)
Definition BLI_bounds.hh:55
void debug_randomize_curve_order(bke::CurvesGeometry *curves)
Definition randomize.cc:193
MatT from_scale(const VecBase< typename MatT::base_type, ScaleDim > &scale)
static Map< int, int > create_curve_instances(GeoNodeExecParams &params, TextLayout &layout, bke::Instances &instances)
static void add_instances_from_handles(bke::Instances &instances, const Map< int, int > &char_handles, const TextLayout &layout)
static void create_attributes(GeoNodeExecParams &params, const TextLayout &layout, bke::Instances &instances)
static void node_init(bNodeTree *, bNode *node)
static float3 get_pivot_point(GeoNodeExecParams &params, bke::CurvesGeometry &curves)
static void node_layout(uiLayout *layout, bContext *C, PointerRNA *ptr)
static std::optional< TextLayout > get_text_layout(GeoNodeExecParams &params)
static void node_geo_exec(GeoNodeExecParams params)
static void node_declare(NodeDeclarationBuilder &b)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
MatBase< float, 4, 4 > float4x4
VecBase< float, 2 > float2
VecBase< float, 3 > float3
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:42
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:54
#define min(a, b)
Definition sort.cc:36
static void text_free(SpaceLink *sl)
Definition space_text.cc:91
short linenr
Definition BKE_vfont.hh:22
float yof
Definition BKE_vfont.hh:20
float xof
Definition BKE_vfont.hh:20
uint is_overflow
Definition BKE_vfont.hh:25
struct VFont * vfont
short resolu
ListBase nurb
short ob_type
CurvesGeometry geometry
Definition DNA_ID.h:404
struct ID * id
void * storage
Defines a node type.
Definition BKE_node.hh:226
std::string ui_description
Definition BKE_node.hh:232
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:277
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:347
const char * enum_name_legacy
Definition BKE_node.hh:235
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:355
static GeometrySet from_instances(Instances *instances, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
static GeometrySet from_curves(Curves *curves, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
i
Definition text_draw.cc:230
max
Definition text_draw.cc:251
uint len
ParamHandle ** handles
PointerRNA * ptr
Definition wm_files.cc:4226