Blender  V3.3
node_geo_string_to_curves.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
3 #include "DNA_curve_types.h"
4 #include "DNA_vfont_types.h"
5 
6 #include "BKE_curve.h"
8 #include "BKE_curves.hh"
9 #include "BKE_vfont.h"
10 
11 #include "BLI_hash.h"
12 #include "BLI_string_utf8.h"
13 #include "BLI_task.hh"
14 
15 #include "UI_interface.h"
16 #include "UI_resources.h"
17 
18 #include "node_geometry_util.hh"
19 
21 
23 
25 {
26  b.add_input<decl::String>(N_("String"));
27  b.add_input<decl::Float>(N_("Size")).default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE);
28  b.add_input<decl::Float>(N_("Character Spacing"))
29  .default_value(1.0f)
30  .min(0.0f)
31  .subtype(PROP_DISTANCE);
32  b.add_input<decl::Float>(N_("Word Spacing"))
33  .default_value(1.0f)
34  .min(0.0f)
35  .subtype(PROP_DISTANCE);
36  b.add_input<decl::Float>(N_("Line Spacing"))
37  .default_value(1.0f)
38  .min(0.0f)
39  .subtype(PROP_DISTANCE);
40  b.add_input<decl::Float>(N_("Text Box Width"))
41  .default_value(0.0f)
42  .min(0.0f)
43  .subtype(PROP_DISTANCE);
44  b.add_input<decl::Float>(N_("Text Box Height"))
45  .default_value(0.0f)
46  .min(0.0f)
47  .subtype(PROP_DISTANCE)
48  .make_available([](bNode &node) {
49  node_storage(node).overflow = GEO_NODE_STRING_TO_CURVES_MODE_SCALE_TO_FIT;
50  });
51  b.add_output<decl::Geometry>(N_("Curve Instances"));
52  b.add_output<decl::String>(N_("Remainder")).make_available([](bNode &node) {
53  node_storage(node).overflow = GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE;
54  });
55  b.add_output<decl::Int>(N_("Line")).field_source();
56  b.add_output<decl::Vector>(N_("Pivot Point")).field_source();
57 }
58 
59 static void node_layout(uiLayout *layout, struct bContext *C, PointerRNA *ptr)
60 {
61  uiLayoutSetPropSep(layout, true);
62  uiLayoutSetPropDecorate(layout, false);
63  uiTemplateID(layout,
64  C,
65  ptr,
66  "font",
67  nullptr,
68  "FONT_OT_open",
69  "FONT_OT_unlink",
71  false,
72  nullptr);
73  uiItemR(layout, ptr, "overflow", 0, "", ICON_NONE);
74  uiItemR(layout, ptr, "align_x", 0, "", ICON_NONE);
75  uiItemR(layout, ptr, "align_y", 0, "", ICON_NONE);
76  uiItemR(layout, ptr, "pivot_mode", 0, IFACE_("Pivot Point"), ICON_NONE);
77 }
78 
80 {
81  NodeGeometryStringToCurves *data = MEM_cnew<NodeGeometryStringToCurves>(__func__);
82 
87  node->storage = data;
88  node->id = (ID *)BKE_vfont_builtin_get();
89 }
90 
92 {
93  const NodeGeometryStringToCurves &storage = node_storage(*node);
95  storage.overflow;
96  bNodeSocket *socket_remainder = ((bNodeSocket *)node->outputs.first)->next;
98  ntree, socket_remainder, overflow == GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE);
99 
100  bNodeSocket *height_socket = (bNodeSocket *)node->inputs.last;
102  ntree, height_socket, overflow != GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW);
103 }
104 
106 {
107  const NodeGeometryStringToCurves &storage = node_storage(params.node());
109  storage.pivot_mode;
110 
111  float3 min(FLT_MAX), max(FLT_MIN);
112 
113  /* Check if curve is empty. */
114  if (!curves.bounds_min_max(min, max)) {
115  return {0.0f, 0.0f, 0.0f};
116  }
117 
118  switch (pivot_mode) {
120  return (min + max) / 2;
122  return float3(min.x, min.y, 0.0f);
124  return float3((min.x + max.x) / 2, min.y, 0.0f);
126  return float3(max.x, min.y, 0.0f);
128  return float3(min.x, max.y, 0.0f);
130  return float3((min.x + max.x) / 2, max.y, 0.0f);
132  return float3(max.x, max.y, 0.0f);
133  }
134  return {0.0f, 0.0f, 0.0f};
135 }
136 
137 struct TextLayout {
138  /* Position of each character. */
140 
141  /* Line number of each character. */
143 
144  /* Map of Pivot point for each character code. */
146 
147  /* UTF-32 Character codes. */
149 
150  /* The text that fit into the text box, with newline character sequences replaced. */
151  std::string text;
152 
153  /* The text that didn't fit into the text box in 'Truncate' mode. May be empty. */
154  std::string truncated_text;
155 
156  /* Font size could be modified if in 'Scale to fit'-mode. */
158 };
159 
160 static std::optional<TextLayout> get_text_layout(GeoNodeExecParams &params)
161 {
162  VFont *vfont = reinterpret_cast<VFont *>(params.node().id);
163  if (!vfont) {
164  params.error_message_add(NodeWarningType::Error, TIP_("Font not specified"));
165  return std::nullopt;
166  }
167 
168  TextLayout layout;
169  layout.text = params.extract_input<std::string>("String");
170  if (layout.text.empty()) {
171  return std::nullopt;
172  }
173 
174  const NodeGeometryStringToCurves &storage = node_storage(params.node());
176  storage.overflow;
178  storage.align_x;
180  storage.align_y;
181 
182  const float font_size = std::max(params.extract_input<float>("Size"), 0.0f);
183  const float char_spacing = params.extract_input<float>("Character Spacing");
184  const float word_spacing = params.extract_input<float>("Word Spacing");
185  const float line_spacing = params.extract_input<float>("Line Spacing");
186  const float textbox_w = params.extract_input<float>("Text Box Width");
187  const float textbox_h = overflow == GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW ?
188  0.0f :
189  params.extract_input<float>("Text Box Height");
190 
191  Curve cu = dna::shallow_zero_initialize();
192  cu.type = OB_FONT;
193  /* Set defaults */
194  cu.resolu = 12;
195  cu.smallcaps_scale = 0.75f;
196  cu.wordspace = 1.0f;
197  /* Set values from inputs */
198  cu.spacemode = align_x;
199  cu.align_y = align_y;
200  cu.fsize = font_size;
201  cu.spacing = char_spacing;
202  cu.wordspace = word_spacing;
203  cu.linedist = line_spacing;
204  cu.vfont = vfont;
205  cu.overflow = overflow;
206  cu.tb = (TextBox *)MEM_calloc_arrayN(MAXTEXTBOX, sizeof(TextBox), __func__);
207  cu.tb->w = textbox_w;
208  cu.tb->h = textbox_h;
209  cu.totbox = 1;
210  size_t len_bytes;
211  size_t len_chars = BLI_strlen_utf8_ex(layout.text.c_str(), &len_bytes);
212  cu.len_char32 = len_chars;
213  cu.len = len_bytes;
214  cu.pos = len_chars;
215  /* The reason for the additional character here is unknown, but reflects other code elsewhere. */
216  cu.str = (char *)MEM_mallocN(len_bytes + sizeof(char32_t), __func__);
217  cu.strinfo = (CharInfo *)MEM_callocN((len_chars + 1) * sizeof(CharInfo), __func__);
218  BLI_strncpy(cu.str, layout.text.c_str(), len_bytes + 1);
219 
220  struct CharTrans *chartransdata = nullptr;
221  int text_len;
222  bool text_free;
223  const char32_t *r_text = nullptr;
224  /* Mode FO_DUPLI used because it doesn't create curve splines. */
226  nullptr, &cu, FO_DUPLI, nullptr, &r_text, &text_len, &text_free, &chartransdata);
227 
228  if (text_free) {
229  MEM_freeN((void *)r_text);
230  }
231 
232  Span<CharInfo> info{cu.strinfo, text_len};
233  layout.final_font_size = cu.fsize_realtime;
234  layout.positions.reserve(text_len);
235 
236  for (const int i : IndexRange(text_len)) {
237  CharTrans &ct = chartransdata[i];
238  layout.positions.append(float2(ct.xof, ct.yof) * layout.final_font_size);
239 
240  if ((info[i].flag & CU_CHINFO_OVERFLOW) && (cu.overflow == CU_OVERFLOW_TRUNCATE)) {
241  const int offset = BLI_str_utf8_offset_from_index(layout.text.c_str(), i + 1);
242  layout.truncated_text = layout.text.substr(offset);
243  layout.text = layout.text.substr(0, offset);
244  break;
245  }
246  }
247 
248  if (params.output_is_required("Line")) {
249  layout.line_numbers.reinitialize(layout.positions.size());
250  for (const int i : layout.positions.index_range()) {
251  CharTrans &ct = chartransdata[i];
252  layout.line_numbers[i] = ct.linenr;
253  }
254  }
255 
256  /* Convert UTF-8 encoded string to UTF-32. */
257  len_chars = BLI_strlen_utf8_ex(layout.text.c_str(), &len_bytes);
258  layout.char_codes.resize(len_chars + 1);
259  BLI_str_utf8_as_utf32(layout.char_codes.data(), layout.text.c_str(), layout.char_codes.size());
260  layout.char_codes.remove_last();
261 
262  MEM_SAFE_FREE(chartransdata);
263  MEM_SAFE_FREE(cu.str);
264  MEM_SAFE_FREE(cu.strinfo);
265  MEM_SAFE_FREE(cu.tb);
266 
267  return layout;
268 }
269 
270 /* Returns a mapping of UTF-32 character code to instance handle. */
272  TextLayout &layout,
273  InstancesComponent &instances)
274 {
275  VFont *vfont = (VFont *)params.node().id;
277  bool pivot_required = params.output_is_required("Pivot Point");
278 
279  for (int i : layout.char_codes.index_range()) {
280  if (handles.contains(layout.char_codes[i])) {
281  continue;
282  }
283  Curve cu = dna::shallow_zero_initialize();
284  cu.type = OB_FONT;
285  cu.resolu = 12;
286  cu.vfont = vfont;
287  CharInfo charinfo = {0};
288  charinfo.mat_nr = 1;
289 
290  BKE_vfont_build_char(&cu, &cu.nurb, layout.char_codes[i], &charinfo, 0, 0, 0, i, 1);
291  Curves *curves_id = bke::curve_legacy_to_curves(cu);
292  if (curves_id == nullptr) {
293  if (pivot_required) {
294  layout.pivot_points.add_new(layout.char_codes[i], float3(0));
295  }
296  handles.add_new(layout.char_codes[i], instances.add_reference({}));
297  continue;
298  }
299 
301  BKE_nurbList_free(&cu.nurb);
302 
303  float4x4 size_matrix = float4x4::identity();
304  size_matrix.apply_scale(layout.final_font_size);
305  curves.transform(size_matrix);
306 
307  if (pivot_required) {
308  float3 pivot_point = get_pivot_point(params, curves);
309  layout.pivot_points.add_new(layout.char_codes[i], pivot_point);
310  }
311 
312  GeometrySet geometry_set = GeometrySet::create_with_curves(curves_id);
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.instance_transforms();
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] = float4x4::from_location({layout.positions[i].x, layout.positions[i].y, 0});
330  }
331  });
332 }
333 
335  const TextLayout &layout,
336  InstancesComponent &instances)
337 {
338  MutableAttributeAccessor attributes = *instances.attributes_for_write();
339 
340  if (params.output_is_required("Line")) {
342  SpanAttributeWriter<int> line_attribute = attributes.lookup_or_add_for_write_only_span<int>(
343  line_id.get(), ATTR_DOMAIN_INSTANCE);
344  line_attribute.span.copy_from(layout.line_numbers);
345  line_attribute.finish();
346  params.set_output("Line",
347  AnonymousAttributeFieldInput::Create<int>(std::move(line_id),
348  params.attribute_producer_name()));
349  }
350 
351  if (params.output_is_required("Pivot Point")) {
353  SpanAttributeWriter<float3> pivot_attribute =
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  params.set_output("Pivot Point",
362  AnonymousAttributeFieldInput::Create<float3>(
363  std::move(pivot_id), params.attribute_producer_name()));
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.size() == 0) {
382  params.set_output("Curve Instances", GeometrySet());
383  params.set_default_remaining_outputs();
384  return;
385  }
386 
387  /* Create and add instances. */
388  GeometrySet geometry_set_out;
389  InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>();
390  Map<int, int> char_handles = create_curve_instances(params, *layout, instances);
391  add_instances_from_handles(instances, char_handles, *layout);
392  create_attributes(params, *layout, instances);
393 
394  params.set_output("Curve Instances", std::move(geometry_set_out));
395 }
396 
397 } // namespace blender::nodes::node_geo_string_to_curves_cc
398 
400 {
402 
403  static bNodeType ntype;
404 
410  node_type_size(&ntype, 190, 120, 700);
411  node_type_storage(&ntype,
412  "NodeGeometryStringToCurves",
416  nodeRegisterType(&ntype);
417 }
@ ATTR_DOMAIN_INSTANCE
Definition: BKE_attribute.h:32
void BKE_nurbList_free(struct ListBase *lb)
Definition: curve.cc:649
Low-level operations for curves.
void node_type_size(struct bNodeType *ntype, int width, int minwidth, int maxwidth)
Definition: node.cc:4396
void node_type_update(struct bNodeType *ntype, void(*updatefunc)(struct bNodeTree *ntree, struct bNode *node))
Definition: node.cc:4443
#define NODE_STORAGE_FUNCS(StorageT)
Definition: BKE_node.h:1563
void nodeSetSocketAvailability(struct bNodeTree *ntree, struct bNodeSocket *sock, bool is_available)
Definition: node.cc:3664
void node_type_init(struct bNodeType *ntype, void(*initfunc)(struct bNodeTree *ntree, struct bNode *node))
Definition: node.cc:4390
#define NODE_CLASS_GEOMETRY
Definition: BKE_node.h:359
void node_type_storage(struct bNodeType *ntype, const char *storagename, void(*freefunc)(struct bNode *node), void(*copyfunc)(struct bNodeTree *dest_ntree, struct bNode *dest_node, const struct bNode *src_node))
Definition: node.cc:4426
#define GEO_NODE_STRING_TO_CURVES
Definition: BKE_node.h:1433
void nodeRegisterType(struct bNodeType *ntype)
Definition: node.cc:1357
struct VFont * BKE_vfont_builtin_get(void)
Definition: vfont.c:413
void BKE_vfont_build_char(struct Curve *cu, struct ListBase *nubase, unsigned int character, struct CharInfo *info, float ofsx, float ofsy, float rot, int charidx, float fsize)
Definition: vfont.c:495
bool BKE_vfont_to_curve_ex(struct Object *ob, struct Curve *cu, int mode, struct ListBase *r_nubase, const char32_t **r_text, int *r_text_len, bool *r_text_free, struct CharTrans **r_chartransdata)
Definition: vfont.c:1709
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t maxncpy) ATTR_NONNULL()
Definition: string.c:64
size_t BLI_str_utf8_as_utf32(char32_t *__restrict dst_w, const char *__restrict src_c, size_t maxncpy) ATTR_NONNULL(1
int BLI_str_utf8_offset_from_index(const char *str, int index) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition: string_utf8.c:771
size_t BLI_strlen_utf8_ex(const char *strc, size_t *r_len_bytes) ATTR_NONNULL(1
#define UNUSED(x)
#define TIP_(msgid)
#define IFACE_(msgid)
#define MAXTEXTBOX
struct CharInfo CharInfo
@ CU_CHINFO_OVERFLOW
@ 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 FO_DUPLI
#define MEM_SAFE_FREE(v)
Group Output data from inside of a node group A color picker Mix two input colors RGB to Convert a color s luminance to a grayscale value Generate a normal vector and a dot product Bright Control the brightness and contrast of the input color Vector Map an input vectors to curves
@ PROP_DISTANCE
Definition: RNA_types.h:149
#define C
Definition: RandGen.cpp:25
void uiLayoutSetPropSep(uiLayout *layout, bool is_sep)
void uiItemR(uiLayout *layout, struct PointerRNA *ptr, const char *propname, int flag, const char *name, int icon)
void uiLayoutSetPropDecorate(uiLayout *layout, bool is_sep)
void uiTemplateID(uiLayout *layout, const struct bContext *C, struct PointerRNA *ptr, const char *propname, const char *newop, const char *openop, const char *unlinkop, int filter, bool live_icon, const char *text)
@ UI_TEMPLATE_ID_FILTER_ALL
blender::Span< int > instance_reference_handles() const
std::optional< blender::bke::MutableAttributeAccessor > attributes_for_write() final
int add_reference(const InstanceReference &reference)
blender::MutableSpan< blender::float4x4 > instance_transforms()
void reinitialize(const int64_t new_size)
Definition: BLI_array.hh:387
int64_t size() const
Definition: BLI_vector.hh:694
IndexRange index_range() const
Definition: BLI_vector.hh:920
void resize(const int64_t new_size)
Definition: BLI_vector.hh:353
void remove_last()
Definition: BLI_vector.hh:715
static CurvesGeometry & wrap(::CurvesGeometry &dna_struct)
Definition: BKE_curves.hh:138
GSpanAttributeWriter lookup_or_add_for_write_only_span(const AttributeIDRef &attribute_id, const eAttrDomain domain, const eCustomDataType data_type)
OperationNode * node
bNodeTree * ntree
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
ccl_gpu_kernel_postfix ccl_global float int int int int float bool int offset
void(* MEM_freeN)(void *vmemh)
Definition: mallocn.c:27
void *(* MEM_calloc_arrayN)(size_t len, size_t size, const char *str)
Definition: mallocn.c:32
void *(* MEM_callocN)(size_t len, const char *str)
Definition: mallocn.c:31
void *(* MEM_mallocN)(size_t len, const char *str)
Definition: mallocn.c:33
static ulong * next
Curves * curve_legacy_to_curves(const Curve &curve_legacy)
OwnedAnonymousAttributeID< true > StrongAnonymousAttributeID
static void node_layout(uiLayout *layout, struct bContext *C, PointerRNA *ptr)
static float3 get_pivot_point(GeoNodeExecParams &params, bke::CurvesGeometry &curves)
static std::optional< TextLayout > get_text_layout(GeoNodeExecParams &params)
static Map< int, int > create_curve_instances(GeoNodeExecParams &params, TextLayout &layout, InstancesComponent &instances)
static void node_update(bNodeTree *ntree, bNode *node)
static void node_init(bNodeTree *UNUSED(ntree), bNode *node)
static void add_instances_from_handles(InstancesComponent &instances, const Map< int, int > &char_handles, const TextLayout &layout)
static void create_attributes(GeoNodeExecParams &params, const TextLayout &layout, InstancesComponent &instances)
static void node_geo_exec(GeoNodeExecParams params)
static void node_declare(NodeDeclarationBuilder &b)
void parallel_for(IndexRange range, int64_t grain_size, const Function &function)
Definition: BLI_task.hh:51
vec_base< float, 3 > float3
vec_base< float, 2 > float2
static const pxr::TfToken b("b", pxr::TfToken::Immortal)
static void node_init(const struct bContext *C, bNodeTree *ntree, bNode *node)
Definition: node.cc:1082
void register_node_type_geo_string_to_curves()
void geo_node_type_base(bNodeType *ntype, int type, const char *name, short nclass)
void node_copy_standard_storage(bNodeTree *UNUSED(dest_ntree), bNode *dest_node, const bNode *src_node)
Definition: node_util.c:55
void node_free_standard_storage(bNode *node)
Definition: node_util.c:43
#define min(a, b)
Definition: sort.c:35
static void text_free(SpaceLink *sl)
Definition: space_text.c:85
short linenr
Definition: BKE_vfont.h:22
float yof
Definition: BKE_vfont.h:20
float xof
Definition: BKE_vfont.h:20
struct VFont * vfont
short resolu
ListBase nurb
short type
CurvesGeometry geometry
GeometryComponent & get_component_for_write(GeometryComponentType component_type)
static GeometrySet create_with_curves(Curves *curves, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
Definition: DNA_ID.h:368
Defines a node type.
Definition: BKE_node.h:226
NodeGeometryExecFunction geometry_node_execute
Definition: BKE_node.h:316
void(* draw_buttons)(struct uiLayout *, struct bContext *C, struct PointerRNA *ptr)
Definition: BKE_node.h:244
NodeDeclareFunction declare
Definition: BKE_node.h:324
void apply_scale(const float scale)
static float4x4 identity()
Definition: BLI_float4x4.hh:80
float max
ParamHandle ** handles
#define N_(msgid)
PointerRNA * ptr
Definition: wm_files.c:3480