Blender V5.0
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"
20#include "UI_resources.hh"
21
22#include "GEO_randomize.hh"
23
24#include "node_geometry_util.hh"
25
27
29
31{
32 b.add_input<decl::String>("String").optional_label();
33 b.add_input<decl::Float>("Size").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE);
34 b.add_input<decl::Float>("Character Spacing").default_value(1.0f).min(0.0f);
35 b.add_input<decl::Float>("Word Spacing").default_value(1.0f).min(0.0f);
36 b.add_input<decl::Float>("Line Spacing").default_value(1.0f).min(0.0f);
37 b.add_input<decl::Float>("Text Box Width").default_value(0.0f).min(0.0f).subtype(PROP_DISTANCE);
38 auto &height = b.add_input<decl::Float>("Text Box Height")
39 .default_value(0.0f)
40 .min(0.0f)
41 .subtype(PROP_DISTANCE)
42 .make_available([](bNode &node) {
43 node_storage(node).overflow = GEO_NODE_STRING_TO_CURVES_MODE_SCALE_TO_FIT;
44 });
45 b.add_output<decl::Geometry>("Curve Instances");
46 auto &remainder = b.add_output<decl::String>("Remainder").make_available([](bNode &node) {
47 node_storage(node).overflow = GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE;
48 });
49 b.add_output<decl::Int>("Line").field_on_all().translation_context(BLT_I18NCONTEXT_ID_TEXT);
50 b.add_output<decl::Vector>("Pivot Point").field_on_all();
51
52 const bNode *node = b.node_or_null();
53 if (node != nullptr) {
54 const NodeGeometryStringToCurves &storage = node_storage(*node);
56 storage.overflow);
57
58 remainder.available(overflow == GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE);
59 height.available(overflow != GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW);
60 }
61}
62
63static void node_layout(uiLayout *layout, bContext *C, PointerRNA *ptr)
64{
65 layout->use_property_split_set(true);
66 layout->use_property_decorate_set(false);
67 uiTemplateID(layout, C, ptr, "font", nullptr, "FONT_OT_open", "FONT_OT_unlink");
68 layout->prop(ptr, "overflow", UI_ITEM_NONE, "", ICON_NONE);
69 layout->prop(ptr, "align_x", UI_ITEM_NONE, "", ICON_NONE);
70 layout->prop(ptr, "align_y", UI_ITEM_NONE, "", ICON_NONE);
71 layout->prop(ptr, "pivot_mode", UI_ITEM_NONE, IFACE_("Pivot Point"), ICON_NONE);
72}
73
85
87{
88 const NodeGeometryStringToCurves &storage = node_storage(params.node());
90 storage.pivot_mode;
91
92 const std::optional<Bounds<float3>> bounds = bounds::min_max(curves.positions());
93
94 /* Check if curve is empty. */
95 if (!bounds.has_value()) {
96 return {0.0f, 0.0f, 0.0f};
97 }
98 const float3 min = bounds->min;
99 const float3 max = bounds->max;
100
101 switch (pivot_mode) {
103 return (min + max) / 2;
105 return float3(min.x, min.y, 0.0f);
107 return float3((min.x + max.x) / 2, min.y, 0.0f);
109 return float3(max.x, min.y, 0.0f);
111 return float3(min.x, max.y, 0.0f);
113 return float3((min.x + max.x) / 2, max.y, 0.0f);
115 return float3(max.x, max.y, 0.0f);
116 }
117 return {0.0f, 0.0f, 0.0f};
118}
119
121 /* Position of each character. */
123
124 /* Line number of each character. */
126
127 /* Map of Pivot point for each character code. */
129
130 /* UTF32 Character codes. */
132
133 /* The text that fit into the text box, with newline character sequences replaced. */
134 std::string text;
135
136 /* The text that didn't fit into the text box in "Truncate" mode. May be empty. */
137 std::string truncated_text;
138
139 /* Font size could be modified if in "Scale to fit"-mode. */
141};
142
143static std::optional<TextLayout> get_text_layout(GeoNodeExecParams &params)
144{
145 VFont *vfont = reinterpret_cast<VFont *>(params.node().id);
146 if (!vfont) {
147 params.error_message_add(NodeWarningType::Error, TIP_("Font not specified"));
148 return std::nullopt;
149 }
150
151 TextLayout layout;
152 layout.text = params.extract_input<std::string>("String");
153 if (layout.text.empty()) {
154 return std::nullopt;
155 }
156
157 const NodeGeometryStringToCurves &storage = node_storage(params.node());
159 storage.overflow;
161 storage.align_x;
163 storage.align_y;
164
165 const float font_size = std::max(params.extract_input<float>("Size"), 0.0f);
166 const float char_spacing = params.extract_input<float>("Character Spacing");
167 const float word_spacing = params.extract_input<float>("Word Spacing");
168 const float line_spacing = params.extract_input<float>("Line Spacing");
169 const float textbox_w = params.extract_input<float>("Text Box Width");
170 const float textbox_h = overflow == GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW ?
171 0.0f :
172 params.extract_input<float>("Text Box Height");
173
174 Curve cu = dna::shallow_zero_initialize();
175 cu.ob_type = OB_FONT;
176 /* Set defaults */
177 cu.resolu = 12;
178 cu.smallcaps_scale = 0.75f;
179 cu.wordspace = 1.0f;
180 /* Set values from inputs */
181 cu.spacemode = align_x;
182 cu.align_y = align_y;
183 cu.fsize = font_size;
184 cu.spacing = char_spacing;
185 cu.wordspace = word_spacing;
186 cu.linedist = line_spacing;
187 cu.vfont = vfont;
188 cu.overflow = overflow;
189 cu.tb = MEM_calloc_arrayN<TextBox>(MAXTEXTBOX, __func__);
190 cu.tb->w = textbox_w;
191 cu.tb->h = textbox_h;
192 cu.totbox = 1;
193 size_t len_bytes;
194 size_t len_chars = BLI_strlen_utf8_ex(layout.text.c_str(), &len_bytes);
195 cu.len_char32 = len_chars;
196 cu.len = len_bytes;
197 cu.pos = len_chars;
198 /* The reason for the additional character here is unknown, but reflects other code elsewhere. */
199 cu.str = MEM_malloc_arrayN<char>(len_bytes + sizeof(char32_t), __func__);
200 memcpy(cu.str, layout.text.c_str(), len_bytes + 1);
201 cu.strinfo = MEM_calloc_arrayN<CharInfo>(len_chars + 1, __func__);
202
203 CharTrans *chartransdata = nullptr;
204 int text_len;
205 bool text_free;
206 const char32_t *r_text = nullptr;
207 float final_font_size = 0.0f;
208 /* Mode FO_DUPLI used because it doesn't create curve splines. */
209 BKE_vfont_to_curve_ex(nullptr,
210 cu,
211 FO_DUPLI,
212 nullptr,
213 &r_text,
214 &text_len,
215 &text_free,
216 &chartransdata,
217 &final_font_size);
218
219 if (text_free) {
220 MEM_freeN(r_text);
221 }
222
223 Span<CharInfo> info{cu.strinfo, text_len};
224 layout.final_font_size = final_font_size;
225 layout.positions.reserve(text_len);
226
227 for (const int i : IndexRange(text_len)) {
228 CharTrans &ct = chartransdata[i];
229 layout.positions.append(ct.offset * layout.final_font_size);
230
231 if (ct.is_overflow && (cu.overflow == CU_OVERFLOW_TRUNCATE)) {
232 const int offset = BLI_str_utf8_offset_from_index(
233 layout.text.c_str(), layout.text.size(), i + 1);
234 layout.truncated_text = layout.text.substr(offset);
235 layout.text = layout.text.substr(0, offset);
236 break;
237 }
238 }
239
240 if (params.anonymous_attribute_output_is_required("Line")) {
241 layout.line_numbers.reinitialize(layout.positions.size());
242 for (const int i : layout.positions.index_range()) {
243 CharTrans &ct = chartransdata[i];
244 layout.line_numbers[i] = ct.linenr;
245 }
246 }
247
248 /* Convert UTF8 encoded string to UTF32. */
249 len_chars = BLI_strlen_utf8_ex(layout.text.c_str(), &len_bytes);
250 layout.char_codes.resize(len_chars + 1);
251 BLI_str_utf8_as_utf32(layout.char_codes.data(), layout.text.c_str(), layout.char_codes.size());
252 layout.char_codes.remove_last();
253
254 MEM_SAFE_FREE(chartransdata);
255 MEM_SAFE_FREE(cu.str);
256 MEM_SAFE_FREE(cu.strinfo);
257 MEM_SAFE_FREE(cu.tb);
258
259 return layout;
260}
261
264 TextLayout &layout,
265 bke::Instances &instances)
266{
267 VFont *vfont = reinterpret_cast<VFont *>(params.node().id);
269 bool pivot_required = params.anonymous_attribute_output_is_required("Pivot Point");
270
271 for (int i : layout.char_codes.index_range()) {
272 if (handles.contains(layout.char_codes[i])) {
273 continue;
274 }
275 Curve cu = dna::shallow_zero_initialize();
276 cu.ob_type = OB_FONT;
277 cu.resolu = 12;
278 cu.vfont = vfont;
279 CharInfo charinfo = {0};
280 charinfo.mat_nr = 1;
281
282 const float2 char_offset = {0, 0};
284 cu, &cu.nurb, layout.char_codes[i], &charinfo, false, char_offset, 0, i, 1);
285 Curves *curves_id = bke::curve_legacy_to_curves(cu);
286 if (curves_id == nullptr) {
287 if (pivot_required) {
288 layout.pivot_points.add_new(layout.char_codes[i], float3(0));
289 }
290 handles.add_new(layout.char_codes[i], instances.add_reference({}));
291 continue;
292 }
293
294 bke::CurvesGeometry &curves = curves_id->geometry.wrap();
296
298
300 curves.transform(size_matrix);
301
302 if (pivot_required) {
303 float3 pivot_point = get_pivot_point(params, curves);
304 layout.pivot_points.add_new(layout.char_codes[i], pivot_point);
305 }
306
307 GeometrySet geometry_set = GeometrySet::from_curves(curves_id);
308
309 {
310 const char32_t char_code[2] = {layout.char_codes[i], 0};
311 char inserted_utf8[8] = {0};
312 const size_t len = BLI_str_utf32_as_utf8(inserted_utf8, char_code, sizeof(inserted_utf8));
313 geometry_set.name = std::string(inserted_utf8, len);
314 }
315
316 handles.add_new(layout.char_codes[i], instances.add_reference(std::move(geometry_set)));
317 }
318 return handles;
319}
320
322 const Map<int, int> &char_handles,
323 const TextLayout &layout)
324{
325 instances.resize(layout.positions.size());
327 MutableSpan<float4x4> transforms = instances.transforms_for_write();
328
329 threading::parallel_for(IndexRange(layout.positions.size()), 256, [&](IndexRange range) {
330 for (const int i : range) {
331 handles[i] = char_handles.lookup(layout.char_codes[i]);
332 transforms[i] = math::from_location<float4x4>(
333 {layout.positions[i].x, layout.positions[i].y, 0});
334 }
335 });
336}
337
339 const TextLayout &layout,
340 bke::Instances &instances)
341{
342 MutableAttributeAccessor attributes = instances.attributes_for_write();
343
344 if (std::optional<std::string> line_id = params.get_output_anonymous_attribute_id_if_needed(
345 "Line"))
346 {
347 SpanAttributeWriter<int> line_attribute = attributes.lookup_or_add_for_write_only_span<int>(
348 *line_id, AttrDomain::Instance);
349 line_attribute.span.copy_from(layout.line_numbers);
350 line_attribute.finish();
351 }
352
353 if (std::optional<std::string> pivot_id = params.get_output_anonymous_attribute_id_if_needed(
354 "Pivot Point"))
355 {
356 SpanAttributeWriter<float3> pivot_attribute =
357 attributes.lookup_or_add_for_write_only_span<float3>(*pivot_id, AttrDomain::Instance);
358
359 for (const int i : layout.char_codes.index_range()) {
360 pivot_attribute.span[i] = layout.pivot_points.lookup(layout.char_codes[i]);
361 }
362
363 pivot_attribute.finish();
364 }
365}
366
368{
369 std::optional<TextLayout> layout = get_text_layout(params);
370 if (!layout) {
371 params.set_default_remaining_outputs();
372 return;
373 }
374
375 const NodeGeometryStringToCurves &storage =
376 *(const NodeGeometryStringToCurves *)params.node().storage;
378 params.set_output("Remainder", std::move(layout->truncated_text));
379 }
380
381 if (layout->positions.is_empty()) {
382 params.set_output("Curve Instances", GeometrySet());
383 params.set_default_remaining_outputs();
384 return;
385 }
386
387 /* Create and add instances. */
388 std::unique_ptr<bke::Instances> instances = std::make_unique<bke::Instances>();
389 Map<int, int> char_handles = create_curve_instances(params, *layout, *instances);
390 add_instances_from_handles(*instances, char_handles, *layout);
391 create_attributes(params, *layout, *instances);
392
393 params.set_output("Curve Instances", GeometrySet::from_instances(instances.release()));
394}
395
396static void node_register()
397{
398 static blender::bke::bNodeType ntype;
399
400 geo_node_type_base(&ntype, "GeometryNodeStringToCurves", GEO_NODE_STRING_TO_CURVES);
401 ntype.ui_name = "String to Curves";
402 ntype.ui_description =
403 "Generate a paragraph of text with a specific font, using a curve instance to store each "
404 "character";
405 ntype.enum_name_legacy = "STRING_TO_CURVES";
407 ntype.declare = node_declare;
409 ntype.initfunc = node_init;
410 blender::bke::node_type_size(ntype, 190, 120, 700);
412 ntype, "NodeGeometryStringToCurves", node_free_standard_storage, node_copy_standard_storage);
415}
416NOD_REGISTER_NODE(node_register)
417
418} // namespace blender::nodes::node_geo_string_to_curves_cc
void BKE_nurbList_free(ListBase *lb)
Definition curve.cc:597
Low-level operations for curves.
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1240
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:461
#define GEO_NODE_STRING_TO_CURVES
@ FO_DUPLI
Definition BKE_vfont.hh:82
VFont * BKE_vfont_builtin_ensure()
Definition vfont.cc:386
void BKE_vfont_char_build(const Curve &cu, ListBase *nubase, unsigned int charcode, const CharInfo *info, bool is_smallcaps, const blender::float2 &offset, float rotate, int charidx, float fsize)
bool BKE_vfont_to_curve_ex(Object *ob, const 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)
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 MEM_SAFE_FREE(v)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ PROP_DISTANCE
Definition RNA_types.hh:256
#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)
#define UI_ITEM_NONE
BMesh const char void * data
void reinitialize(const int64_t new_size)
Definition BLI_array.hh:419
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:261
void resize(int capacity)
Definition instances.cc:191
bke::MutableAttributeAccessor attributes_for_write()
Definition instances.cc:69
MutableSpan< float4x4 > transforms_for_write()
Definition instances.cc:235
GSpanAttributeWriter lookup_or_add_for_write_only_span(StringRef attribute_id, AttrDomain domain, AttrType data_type)
void make_available(bNode &node) const
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:5384
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
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:5414
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:257
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
blender::float2 offset
Definition BKE_vfont.hh:21
short linenr
Definition BKE_vfont.hh:23
uint is_overflow
Definition BKE_vfont.hh:26
struct VFont * vfont
short resolu
ListBase nurb
short ob_type
CurvesGeometry geometry
Definition DNA_ID.h:414
struct ID * id
void * storage
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:289
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:354
const char * enum_name_legacy
Definition BKE_node.hh:247
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:259
NodeDeclareFunction declare
Definition BKE_node.hh:362
static GeometrySet from_instances(Instances *instances, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
static GeometrySet from_curves(Curves *curves, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
void use_property_decorate_set(bool is_sep)
void use_property_split_set(bool value)
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:4238