Blender V4.3
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)
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();
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 uiItemR(layout, ptr, "overflow", UI_ITEM_NONE, "", ICON_NONE);
68 uiItemR(layout, ptr, "align_x", UI_ITEM_NONE, "", ICON_NONE);
69 uiItemR(layout, ptr, "align_y", UI_ITEM_NONE, "", ICON_NONE);
70 uiItemR(layout, ptr, "pivot_mode", UI_ITEM_NONE, IFACE_("Pivot Point"), ICON_NONE);
71}
72
73static void node_init(bNodeTree * /*tree*/, bNode *node)
74{
75 NodeGeometryStringToCurves *data = MEM_cnew<NodeGeometryStringToCurves>(__func__);
76
81 node->storage = data;
82 node->id = reinterpret_cast<ID *>(BKE_vfont_builtin_get());
83}
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 /* UTF-32 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.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 = static_cast<TextBox *>(MEM_calloc_arrayN(MAXTEXTBOX, sizeof(TextBox), __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 = static_cast<char *>(MEM_mallocN(len_bytes + sizeof(char32_t), __func__));
199 memcpy(cu.str, layout.text.c_str(), len_bytes + 1);
200 cu.strinfo = static_cast<CharInfo *>(MEM_callocN((len_chars + 1) * sizeof(CharInfo), __func__));
201
202 CharTrans *chartransdata = nullptr;
203 int text_len;
204 bool text_free;
205 const char32_t *r_text = nullptr;
206 /* Mode FO_DUPLI used because it doesn't create curve splines. */
208 nullptr, &cu, FO_DUPLI, nullptr, &r_text, &text_len, &text_free, &chartransdata);
209
210 if (text_free) {
211 MEM_freeN(const_cast<char32_t *>(r_text));
212 }
213
214 Span<CharInfo> info{cu.strinfo, text_len};
215 layout.final_font_size = cu.fsize_realtime;
216 layout.positions.reserve(text_len);
217
218 for (const int i : IndexRange(text_len)) {
219 CharTrans &ct = chartransdata[i];
220 layout.positions.append(float2(ct.xof, ct.yof) * layout.final_font_size);
221
222 if ((info[i].flag & CU_CHINFO_OVERFLOW) && (cu.overflow == CU_OVERFLOW_TRUNCATE)) {
223 const int offset = BLI_str_utf8_offset_from_index(
224 layout.text.c_str(), layout.text.size(), i + 1);
225 layout.truncated_text = layout.text.substr(offset);
226 layout.text = layout.text.substr(0, offset);
227 break;
228 }
229 }
230
231 if (params.anonymous_attribute_output_is_required("Line")) {
232 layout.line_numbers.reinitialize(layout.positions.size());
233 for (const int i : layout.positions.index_range()) {
234 CharTrans &ct = chartransdata[i];
235 layout.line_numbers[i] = ct.linenr;
236 }
237 }
238
239 /* Convert UTF-8 encoded string to UTF-32. */
240 len_chars = BLI_strlen_utf8_ex(layout.text.c_str(), &len_bytes);
241 layout.char_codes.resize(len_chars + 1);
242 BLI_str_utf8_as_utf32(layout.char_codes.data(), layout.text.c_str(), layout.char_codes.size());
243 layout.char_codes.remove_last();
244
245 MEM_SAFE_FREE(chartransdata);
246 MEM_SAFE_FREE(cu.str);
247 MEM_SAFE_FREE(cu.strinfo);
248 MEM_SAFE_FREE(cu.tb);
249
250 return layout;
251}
252
253/* Returns a mapping of UTF-32 character code to instance handle. */
255 TextLayout &layout,
256 bke::Instances &instances)
257{
258 VFont *vfont = reinterpret_cast<VFont *>(params.node().id);
260 bool pivot_required = params.anonymous_attribute_output_is_required("Pivot Point");
261
262 for (int i : layout.char_codes.index_range()) {
263 if (handles.contains(layout.char_codes[i])) {
264 continue;
265 }
266 Curve cu = dna::shallow_zero_initialize();
267 cu.type = OB_FONT;
268 cu.resolu = 12;
269 cu.vfont = vfont;
270 CharInfo charinfo = {0};
271 charinfo.mat_nr = 1;
272
273 BKE_vfont_build_char(&cu, &cu.nurb, layout.char_codes[i], &charinfo, 0, 0, 0, i, 1);
274 Curves *curves_id = bke::curve_legacy_to_curves(cu);
275 if (curves_id == nullptr) {
276 if (pivot_required) {
277 layout.pivot_points.add_new(layout.char_codes[i], float3(0));
278 }
279 handles.add_new(layout.char_codes[i], instances.add_reference({}));
280 continue;
281 }
282
283 bke::CurvesGeometry &curves = curves_id->geometry.wrap();
285
287
289 curves.transform(size_matrix);
290
291 if (pivot_required) {
292 float3 pivot_point = get_pivot_point(params, curves);
293 layout.pivot_points.add_new(layout.char_codes[i], pivot_point);
294 }
295
296 GeometrySet geometry_set = GeometrySet::from_curves(curves_id);
297 handles.add_new(layout.char_codes[i], instances.add_reference(std::move(geometry_set)));
298 }
299 return handles;
300}
301
303 const Map<int, int> &char_handles,
304 const TextLayout &layout)
305{
306 instances.resize(layout.positions.size());
307 MutableSpan<int> handles = instances.reference_handles_for_write();
308 MutableSpan<float4x4> transforms = instances.transforms_for_write();
309
310 threading::parallel_for(IndexRange(layout.positions.size()), 256, [&](IndexRange range) {
311 for (const int i : range) {
312 handles[i] = char_handles.lookup(layout.char_codes[i]);
313 transforms[i] = math::from_location<float4x4>(
314 {layout.positions[i].x, layout.positions[i].y, 0});
315 }
316 });
317}
318
320 const TextLayout &layout,
321 bke::Instances &instances)
322{
323 MutableAttributeAccessor attributes = instances.attributes_for_write();
324
325 if (std::optional<std::string> line_id = params.get_output_anonymous_attribute_id_if_needed(
326 "Line"))
327 {
328 SpanAttributeWriter<int> line_attribute = attributes.lookup_or_add_for_write_only_span<int>(
329 *line_id, AttrDomain::Instance);
330 line_attribute.span.copy_from(layout.line_numbers);
331 line_attribute.finish();
332 }
333
334 if (std::optional<std::string> pivot_id = params.get_output_anonymous_attribute_id_if_needed(
335 "Pivot Point"))
336 {
337 SpanAttributeWriter<float3> pivot_attribute =
338 attributes.lookup_or_add_for_write_only_span<float3>(*pivot_id, AttrDomain::Instance);
339
340 for (const int i : layout.char_codes.index_range()) {
341 pivot_attribute.span[i] = layout.pivot_points.lookup(layout.char_codes[i]);
342 }
343
344 pivot_attribute.finish();
345 }
346}
347
349{
350 std::optional<TextLayout> layout = get_text_layout(params);
351 if (!layout) {
352 params.set_default_remaining_outputs();
353 return;
354 }
355
356 const NodeGeometryStringToCurves &storage =
357 *(const NodeGeometryStringToCurves *)params.node().storage;
359 params.set_output("Remainder", std::move(layout->truncated_text));
360 }
361
362 if (layout->positions.is_empty()) {
363 params.set_output("Curve Instances", GeometrySet());
364 params.set_default_remaining_outputs();
365 return;
366 }
367
368 /* Create and add instances. */
369 std::unique_ptr<bke::Instances> instances = std::make_unique<bke::Instances>();
370 Map<int, int> char_handles = create_curve_instances(params, *layout, *instances);
371 add_instances_from_handles(*instances, char_handles, *layout);
372 create_attributes(params, *layout, *instances);
373
374 params.set_output("Curve Instances", GeometrySet::from_instances(instances.release()));
375}
376
377static void node_register()
378{
379 static blender::bke::bNodeType ntype;
380
381 geo_node_type_base(&ntype, GEO_NODE_STRING_TO_CURVES, "String to Curves", NODE_CLASS_GEOMETRY);
382 ntype.declare = node_declare;
384 ntype.initfunc = node_init;
385 blender::bke::node_type_size(&ntype, 190, 120, 700);
387 "NodeGeometryStringToCurves",
392}
393NOD_REGISTER_NODE(node_register)
394
395} // namespace blender::nodes::node_geo_string_to_curves_cc
void BKE_nurbList_free(ListBase *lb)
Definition curve.cc:596
Low-level operations for curves.
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1799
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:418
@ FO_DUPLI
Definition BKE_vfont.hh:73
VFont * BKE_vfont_builtin_get()
Definition vfont.cc:422
void BKE_vfont_build_char(Curve *cu, ListBase *nubase, unsigned int character, const CharInfo *info, float ofsx, float ofsy, float rot, int charidx, float fsize)
Definition vfont.cc:507
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)
Definition vfont.cc:1983
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 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 TIP_(msgid)
#define IFACE_(msgid)
#define MAXTEXTBOX
@ CU_OVERFLOW_TRUNCATE
@ CU_CHINFO_OVERFLOW
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:159
void uiLayoutSetPropSep(uiLayout *layout, bool is_sep)
#define UI_ITEM_NONE
void uiLayoutSetPropDecorate(uiLayout *layout, bool is_sep)
void uiTemplateID(uiLayout *layout, const bContext *C, PointerRNA *ptr, const char *propname, const char *newop, const char *openop, const char *unlinkop, int filter=UI_TEMPLATE_ID_FILTER_ALL, bool live_icon=false, const char *text=nullptr)
void uiItemR(uiLayout *layout, PointerRNA *ptr, const char *propname, eUI_Item_Flag flag, const char *name, int icon)
void reinitialize(const int64_t new_size)
Definition BLI_array.hh:388
int64_t size() const
IndexRange index_range() const
void resize(const int64_t new_size)
local_group_size(16, 16) .push_constant(Type b
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void *(* MEM_mallocN)(size_t len, const char *str)
Definition mallocn.cc:44
void *(* MEM_calloc_arrayN)(size_t len, size_t size, const char *str)
Definition mallocn.cc:43
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
void *(* MEM_callocN)(size_t len, const char *str)
Definition mallocn.cc:42
Curves * curve_legacy_to_curves(const Curve &curve_legacy)
void node_type_size(bNodeType *ntype, int width, int minwidth, int maxwidth)
Definition node.cc:4602
void node_type_storage(bNodeType *ntype, const char *storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:4632
void node_register_type(bNodeType *ntype)
Definition node.cc:1708
std::optional< Bounds< T > > min_max(const std::optional< Bounds< T > > &a, const T &b)
Definition BLI_bounds.hh:46
void debug_randomize_curve_order(bke::CurvesGeometry *curves)
Definition randomize.cc:196
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:95
VecBase< float, 2 > float2
VecBase< float, 3 > float3
void geo_node_type_base(blender::bke::bNodeType *ntype, int type, const char *name, short nclass)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:46
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:58
#define min(a, b)
Definition sort.c:32
static void text_free(SpaceLink *sl)
Definition space_text.cc:90
short linenr
Definition BKE_vfont.hh:21
float yof
Definition BKE_vfont.hh:19
float xof
Definition BKE_vfont.hh:19
struct VFont * vfont
short resolu
ListBase nurb
CurvesGeometry geometry
Definition DNA_ID.h:413
static GeometrySet from_curves(Curves *curves, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
Defines a node type.
Definition BKE_node.hh:218
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:267
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:339
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:238
NodeDeclareFunction declare
Definition BKE_node.hh:347
ParamHandle ** handles
PointerRNA * ptr
Definition wm_files.cc:4126
uint8_t flag
Definition wm_window.cc:138