diff options
-rw-r--r-- | release/scripts/startup/nodeitems_builtins.py | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_font.h | 9 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_node.h | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/font.c | 20 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/node.cc | 1 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_float4x4.hh | 7 | ||||
-rw-r--r-- | source/blender/depsgraph/intern/builder/deg_builder_nodes.cc | 15 | ||||
-rw-r--r-- | source/blender/depsgraph/intern/builder/deg_builder_nodes.h | 2 | ||||
-rw-r--r-- | source/blender/depsgraph/intern/builder/deg_builder_relations.cc | 15 | ||||
-rw-r--r-- | source/blender/depsgraph/intern/builder/deg_builder_relations.h | 2 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_node_types.h | 32 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_nodetree.c | 114 | ||||
-rw-r--r-- | source/blender/nodes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/nodes/NOD_geometry.h | 1 | ||||
-rw-r--r-- | source/blender/nodes/NOD_static_types.h | 1 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc | 307 |
16 files changed, 519 insertions, 10 deletions
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index 36c6f0061e8..715874545a5 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -607,6 +607,7 @@ geometry_node_categories = [ NodeItem("FunctionNodeValueToString"), NodeItem("GeometryNodeStringJoin"), NodeItem("FunctionNodeInputSpecialCharacters"), + NodeItem("GeometryNodeStringToCurves"), ]), GeometryNodeCategory("GEO_UTILITIES", "Utilities", items=[ NodeItem("ShaderNodeMapRange"), diff --git a/source/blender/blenkernel/BKE_font.h b/source/blender/blenkernel/BKE_font.h index 522d3843bb2..827ae1b6a0f 100644 --- a/source/blender/blenkernel/BKE_font.h +++ b/source/blender/blenkernel/BKE_font.h @@ -85,6 +85,15 @@ bool BKE_vfont_to_curve_ex(struct Object *ob, struct CharTrans **r_chartransdata); bool BKE_vfont_to_curve_nubase(struct Object *ob, int mode, struct ListBase *r_nubase); bool BKE_vfont_to_curve(struct Object *ob, int mode); +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, + const float fsize); int BKE_vfont_select_get(struct Object *ob, int *r_start, int *r_end); void BKE_vfont_select_clamp(struct Object *ob); diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index bd969400f17..90285d9fc7c 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1501,6 +1501,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_CURVE_PARAMETER 1088 #define GEO_NODE_CURVE_FILLET 1089 #define GEO_NODE_DISTRIBUTE_POINTS_ON_FACES 1090 +#define GEO_NODE_STRING_TO_CURVES 1091 /** \} */ diff --git a/source/blender/blenkernel/intern/font.c b/source/blender/blenkernel/intern/font.c index aa13f86523a..1053b727cbc 100644 --- a/source/blender/blenkernel/intern/font.c +++ b/source/blender/blenkernel/intern/font.c @@ -490,15 +490,15 @@ static void build_underline(Curve *cu, mul_v2_fl(bp[3].vec, font_size); } -static void buildchar(Curve *cu, - ListBase *nubase, - unsigned int character, - CharInfo *info, - float ofsx, - float ofsy, - float rot, - int charidx, - const float fsize) +void BKE_vfont_build_char(Curve *cu, + ListBase *nubase, + unsigned int character, + CharInfo *info, + float ofsx, + float ofsy, + float rot, + int charidx, + const float fsize) { VFontData *vfd = vfont_get_data(which_vfont(cu, info)); if (!vfd) { @@ -1525,7 +1525,7 @@ static bool vfont_to_curve(Object *ob, } /* We do not want to see any character for \n or \r */ if (cha != '\n') { - buildchar(cu, r_nubase, cha, info, ct->xof, ct->yof, ct->rot, i, font_size); + BKE_vfont_build_char(cu, r_nubase, cha, info, ct->xof, ct->yof, ct->rot, i, font_size); } if ((info->flag & CU_CHINFO_UNDERLINE) && (cha != '\n')) { diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 891d6c05a19..bf7c47e9cf0 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -5799,6 +5799,7 @@ static void registerGeometryNodes() register_node_type_geo_material_selection(); register_node_type_geo_separate_components(); register_node_type_geo_set_position(); + register_node_type_geo_string_to_curves(); register_node_type_geo_subdivision_surface(); register_node_type_geo_switch(); register_node_type_geo_transform(); diff --git a/source/blender/blenlib/BLI_float4x4.hh b/source/blender/blenlib/BLI_float4x4.hh index 347ce2caa34..14e61d53845 100644 --- a/source/blender/blenlib/BLI_float4x4.hh +++ b/source/blender/blenlib/BLI_float4x4.hh @@ -45,6 +45,13 @@ struct float4x4 { return mat; } + static float4x4 from_location(const float3 location) + { + float4x4 mat = float4x4::identity(); + copy_v3_v3(mat.values[3], location); + return mat; + } + static float4x4 from_normalized_axis_data(const float3 location, const float3 forward, const float3 up) diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc index 36c6b56caae..463bb02afa4 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc @@ -63,6 +63,7 @@ #include "DNA_sound_types.h" #include "DNA_speaker_types.h" #include "DNA_texture_types.h" +#include "DNA_vfont_types.h" #include "DNA_world_types.h" #include "BKE_action.h" @@ -1764,6 +1765,9 @@ void DepsgraphNodeBuilder::build_nodetree(bNodeTree *ntree) else if (id_type == ID_MC) { build_movieclip((MovieClip *)id); } + else if (id_type == ID_VF) { + build_vfont((VFont *)id); + } else if (ELEM(bnode->type, NODE_GROUP, NODE_CUSTOM_GROUP)) { bNodeTree *group_ntree = (bNodeTree *)id; build_nodetree(group_ntree); @@ -2015,6 +2019,17 @@ void DepsgraphNodeBuilder::build_simulation(Simulation *simulation) }); } +void DepsgraphNodeBuilder::build_vfont(VFont *vfont) +{ + if (built_map_.checkIsBuiltAndTag(vfont)) { + return; + } + build_parameters(&vfont->id); + build_idproperties(vfont->id.properties); + add_operation_node( + &vfont->id, NodeType::GENERIC_DATABLOCK, OperationCode::GENERIC_DATABLOCK_UPDATE); +} + static bool seq_node_build_cb(Sequence *seq, void *user_data) { DepsgraphNodeBuilder *nb = (DepsgraphNodeBuilder *)user_data; diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h index 2378f3fc100..d31290ecbff 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h @@ -55,6 +55,7 @@ struct Scene; struct Simulation; struct Speaker; struct Tex; +struct VFont; struct World; struct bAction; struct bArmature; @@ -235,6 +236,7 @@ class DepsgraphNodeBuilder : public DepsgraphBuilder { virtual void build_scene_sequencer(Scene *scene); virtual void build_scene_audio(Scene *scene); virtual void build_scene_speakers(Scene *scene, ViewLayer *view_layer); + virtual void build_vfont(VFont *vfont); /* Per-ID information about what was already in the dependency graph. * Allows to re-use certain values, to speed up following evaluation. */ diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index 28cfc5a9e1a..55e8c5ed033 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -65,6 +65,7 @@ #include "DNA_sound_types.h" #include "DNA_speaker_types.h" #include "DNA_texture_types.h" +#include "DNA_vfont_types.h" #include "DNA_volume_types.h" #include "DNA_world_types.h" @@ -2496,6 +2497,11 @@ void DepsgraphRelationBuilder::build_nodetree(bNodeTree *ntree) OperationKey clip_key(id, NodeType::PARAMETERS, OperationCode::MOVIECLIP_EVAL); add_relation(clip_key, shading_key, "Clip -> Node"); } + else if (id_type == ID_VF) { + build_vfont((VFont *)id); + ComponentKey vfont_key(id, NodeType::GENERIC_DATABLOCK); + add_relation(vfont_key, shading_key, "VFont -> Node"); + } else if (ELEM(bnode->type, NODE_GROUP, NODE_CUSTOM_GROUP)) { bNodeTree *group_ntree = (bNodeTree *)id; build_nodetree(group_ntree); @@ -2842,6 +2848,15 @@ void DepsgraphRelationBuilder::build_scene_speakers(Scene * /*scene*/, ViewLayer } } +void DepsgraphRelationBuilder::build_vfont(VFont *vfont) +{ + if (built_map_.checkIsBuiltAndTag(vfont)) { + return; + } + build_parameters(&vfont->id); + build_idproperties(vfont->id.properties); +} + void DepsgraphRelationBuilder::build_copy_on_write_relations() { for (IDNode *id_node : graph_->id_nodes) { diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.h b/source/blender/depsgraph/intern/builder/deg_builder_relations.h index 1ad61c25305..f0393544511 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.h @@ -72,6 +72,7 @@ struct Simulation; struct Speaker; struct Tex; struct ViewLayer; +struct VFont; struct World; struct bAction; struct bArmature; @@ -296,6 +297,7 @@ class DepsgraphRelationBuilder : public DepsgraphBuilder { virtual void build_scene_sequencer(Scene *scene); virtual void build_scene_audio(Scene *scene); virtual void build_scene_speakers(Scene *scene, ViewLayer *view_layer); + virtual void build_vfont(VFont *vfont); virtual void build_nested_datablock(ID *owner, ID *id); virtual void build_nested_nodetree(ID *owner, bNodeTree *ntree); diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 6e4737b7d07..35a2dba627a 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1502,6 +1502,16 @@ typedef struct NodeGeometryAttributeCapture { int8_t domain; } NodeGeometryAttributeCapture; +typedef struct NodeGeometryStringToCurves { + /* GeometryNodeStringToCurvesOverflowMode */ + uint8_t overflow; + /* GeometryNodeStringToCurvesAlignXMode */ + uint8_t align_x; + /* GeometryNodeStringToCurvesAlignYMode */ + uint8_t align_y; + char _pad[1]; +} NodeGeometryStringToCurves; + /* script node mode */ #define NODE_SCRIPT_INTERNAL 0 #define NODE_SCRIPT_EXTERNAL 1 @@ -2098,6 +2108,28 @@ typedef enum GeometryNodeCurveFillMode { GEO_NODE_CURVE_FILL_MODE_NGONS = 1, } GeometryNodeCurveFillMode; +typedef enum GeometryNodeStringToCurvesOverflowMode { + GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW = 0, + GEO_NODE_STRING_TO_CURVES_MODE_SCALE_TO_FIT = 1, + GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE = 2, +} GeometryNodeStringToCurvesOverflowMode; + +typedef enum GeometryNodeStringToCurvesAlignXMode { + GEO_NODE_STRING_TO_CURVES_ALIGN_X_LEFT = 0, + GEO_NODE_STRING_TO_CURVES_ALIGN_X_CENTER = 1, + GEO_NODE_STRING_TO_CURVES_ALIGN_X_RIGHT = 2, + GEO_NODE_STRING_TO_CURVES_ALIGN_X_JUSTIFY = 3, + GEO_NODE_STRING_TO_CURVES_ALIGN_X_FLUSH = 4, +} GeometryNodeStringToCurvesAlignXMode; + +typedef enum GeometryNodeStringToCurvesAlignYMode { + GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP_BASELINE = 0, + GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP = 1, + GEO_NODE_STRING_TO_CURVES_ALIGN_Y_MIDDLE = 2, + GEO_NODE_STRING_TO_CURVES_ALIGN_Y_BOTTOM_BASELINE = 3, + GEO_NODE_STRING_TO_CURVES_ALIGN_Y_BOTTOM = 4, +} GeometryNodeStringToCurvesAlignYMode; + #ifdef __cplusplus } #endif diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index dcac157d1a2..333f1584800 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -10425,6 +10425,120 @@ static void def_geo_attribute_capture(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_geo_string_to_curves(StructRNA *srna) +{ + static const EnumPropertyItem rna_node_geometry_string_to_curves_overflow_items[] = { + {GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW, + "OVERFLOW", + ICON_NONE, + "Overflow", + "Let the text use more space than the specified height"}, + {GEO_NODE_STRING_TO_CURVES_MODE_SCALE_TO_FIT, + "SCALE_TO_FIT", + ICON_NONE, + "Scale To Fit", + "Scale the text size to fit inside the width and height"}, + {GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE, + "TRUNCATE", + ICON_NONE, + "Truncate", + "Only output curves that fit within the width and height. Output the remainder to the " + "\"Remainder\" output"}, + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem rna_node_geometry_string_to_curves_align_x_items[] = { + {GEO_NODE_STRING_TO_CURVES_ALIGN_X_LEFT, + "LEFT", + ICON_ALIGN_LEFT, + "Left", + "Align text to the left"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_X_CENTER, + "CENTER", + ICON_ALIGN_CENTER, + "Center", + "Align text to the center"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_X_RIGHT, + "RIGHT", + ICON_ALIGN_RIGHT, + "Right", + "Align text to the right"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_X_JUSTIFY, + "JUSTIFY", + ICON_ALIGN_JUSTIFY, + "Justify", + "Align text to the left and the right"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_X_FLUSH, + "FLUSH", + ICON_ALIGN_FLUSH, + "Flush", + "Align text to the left and the right, with equal character spacing"}, + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem rna_node_geometry_string_to_curves_align_y_items[] = { + {GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP_BASELINE, + "TOP_BASELINE", + ICON_ALIGN_TOP, + "Top Baseline", + "Align text to the top baseline"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP, + "TOP", + ICON_ALIGN_TOP, + "Top", + "Align text to the top"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_Y_MIDDLE, + "MIDDLE", + ICON_ALIGN_MIDDLE, + "Middle", + "Align text to the middle"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_Y_BOTTOM_BASELINE, + "BOTTOM_BASELINE", + ICON_ALIGN_BOTTOM, + "Bottom Baseline", + "Align text to the bottom baseline"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_Y_BOTTOM, + "BOTTOM", + ICON_ALIGN_BOTTOM, + "Bottom", + "Align text to the bottom"}, + {0, NULL, 0, NULL, NULL}, + }; + + PropertyRNA *prop; + + prop = RNA_def_property(srna, "font", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "id"); + RNA_def_property_struct_type(prop, "VectorFont"); + RNA_def_property_ui_text(prop, "Font", "Font of the text. Falls back to the UI font by default"); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + RNA_def_struct_sdna_from(srna, "NodeGeometryStringToCurves", "storage"); + + prop = RNA_def_property(srna, "overflow", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "overflow"); + RNA_def_property_enum_items(prop, rna_node_geometry_string_to_curves_overflow_items); + RNA_def_property_enum_default(prop, GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW); + RNA_def_property_ui_text(prop, "Overflow", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "align_x", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "align_x"); + RNA_def_property_enum_items(prop, rna_node_geometry_string_to_curves_align_x_items); + RNA_def_property_enum_default(prop, GEO_NODE_STRING_TO_CURVES_ALIGN_X_LEFT); + RNA_def_property_ui_text(prop, "Align X", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "align_y", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "align_y"); + RNA_def_property_enum_items(prop, rna_node_geometry_string_to_curves_align_y_items); + RNA_def_property_enum_default(prop, GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP_BASELINE); + RNA_def_property_ui_text(prop, "Align Y", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + /* -------------------------------------------------------------------------- */ static void rna_def_shader_node(BlenderRNA *brna) diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index f8898fbda93..5056c0889bc 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -232,6 +232,7 @@ set(SRC geometry/nodes/node_geo_separate_components.cc geometry/nodes/node_geo_set_position.cc geometry/nodes/node_geo_string_join.cc + geometry/nodes/node_geo_string_to_curves.cc geometry/nodes/node_geo_subdivision_surface.cc geometry/nodes/node_geo_switch.cc geometry/nodes/node_geo_transform.cc diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 9cd3365bd91..823c48f71e3 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -115,6 +115,7 @@ void register_node_type_geo_select_by_handle_type(void); void register_node_type_geo_separate_components(void); void register_node_type_geo_set_position(void); void register_node_type_geo_string_join(void); +void register_node_type_geo_string_to_curves(void); void register_node_type_geo_subdivision_surface(void); void register_node_type_geo_switch(void); void register_node_type_geo_transform(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 170d615b43f..6d71cb8d0b6 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -359,6 +359,7 @@ DefNode(GeometryNode, GEO_NODE_REALIZE_INSTANCES, 0, "REALIZE_INSTANCES", Realiz DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS", SeparateComponents, "Separate Components", "") DefNode(GeometryNode, GEO_NODE_SET_POSITION, 0, "SET_POSITION", SetPosition, "Set Position", "") DefNode(GeometryNode, GEO_NODE_STRING_JOIN, 0, "STRING_JOIN", StringJoin, "String Join", "") +DefNode(GeometryNode, GEO_NODE_STRING_TO_CURVES, def_geo_string_to_curves, "STRING_TO_CURVES", StringToCurves, "String to Curves", "") DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "") DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "") DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "") diff --git a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc new file mode 100644 index 00000000000..0bd050ac3d1 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc @@ -0,0 +1,307 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <codecvt> +#include <locale> + +#include "DNA_curve_types.h" +#include "DNA_vfont_types.h" + +#include "BKE_curve.h" +#include "BKE_font.h" +#include "BKE_spline.hh" + +#include "BLI_hash.h" +#include "BLI_string_utf8.h" +#include "BLI_task.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_string_to_curves_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::String>("String"); + b.add_input<decl::Float>("Size").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Character Spacing") + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Word Spacing").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Line Spacing").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Text Box Width").default_value(0.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Text Box Height").default_value(0.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_output<decl::Geometry>("Curves"); + b.add_output<decl::String>("Remainder"); +} + +static void geo_node_string_to_curves_layout(uiLayout *layout, struct bContext *C, PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiTemplateID(layout, + C, + ptr, + "font", + nullptr, + "FONT_OT_open", + "FONT_OT_unlink", + UI_TEMPLATE_ID_FILTER_ALL, + false, + nullptr); + uiItemR(layout, ptr, "overflow", 0, "", ICON_NONE); + uiItemR(layout, ptr, "align_x", 0, "", ICON_NONE); + uiItemR(layout, ptr, "align_y", 0, "", ICON_NONE); +} + +static void geo_node_string_to_curves_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryStringToCurves *data = (NodeGeometryStringToCurves *)MEM_callocN( + sizeof(NodeGeometryStringToCurves), __func__); + + data->overflow = GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW; + data->align_x = GEO_NODE_STRING_TO_CURVES_ALIGN_X_LEFT; + data->align_y = GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP_BASELINE; + node->storage = data; + node->id = (ID *)BKE_vfont_builtin_get(); +} + +static void geo_node_string_to_curves_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + const NodeGeometryStringToCurves *storage = (const NodeGeometryStringToCurves *)node->storage; + const GeometryNodeStringToCurvesOverflowMode overflow = (GeometryNodeStringToCurvesOverflowMode) + storage->overflow; + bNodeSocket *socket_remainder = ((bNodeSocket *)node->outputs.first)->next; + nodeSetSocketAvailability(socket_remainder, overflow == GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE); + + bNodeSocket *height_socket = (bNodeSocket *)node->inputs.last; + bNodeSocket *width_socket = height_socket->prev; + nodeSetSocketAvailability(height_socket, overflow != GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW); + node_sock_label(width_socket, + overflow == GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW ? N_("Max Width") : + N_("Text Box Width")); +} + +struct TextLayout { + /* Position of each character. */ + Vector<float2> positions; + + /* The text that fit into the text box, with newline character sequences replaced. */ + std::string text; + + /* The text that didn't fit into the text box in 'Truncate' mode. May be empty. */ + std::string truncated_text; + + /* Font size could be modified if in 'Scale to fit'-mode. */ + float final_font_size; +}; + +static TextLayout get_text_layout(GeoNodeExecParams ¶ms) +{ + TextLayout layout; + layout.text = params.extract_input<std::string>("String"); + if (layout.text.empty()) { + return {}; + } + + const NodeGeometryStringToCurves &storage = + *(const NodeGeometryStringToCurves *)params.node().storage; + const GeometryNodeStringToCurvesOverflowMode overflow = (GeometryNodeStringToCurvesOverflowMode) + storage.overflow; + const GeometryNodeStringToCurvesAlignXMode align_x = (GeometryNodeStringToCurvesAlignXMode) + storage.align_x; + const GeometryNodeStringToCurvesAlignYMode align_y = (GeometryNodeStringToCurvesAlignYMode) + storage.align_y; + + const float font_size = std::max(params.extract_input<float>("Size"), 0.0f); + const float char_spacing = params.extract_input<float>("Character Spacing"); + const float word_spacing = params.extract_input<float>("Word Spacing"); + const float line_spacing = params.extract_input<float>("Line Spacing"); + const float textbox_w = params.extract_input<float>("Text Box Width"); + const float textbox_h = overflow == GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW ? + 0.0f : + params.extract_input<float>("Text Box Height"); + VFont *vfont = (VFont *)params.node().id; + + Curve cu = {nullptr}; + cu.type = OB_FONT; + /* Set defaults */ + cu.resolu = 12; + cu.smallcaps_scale = 0.75f; + cu.wordspace = 1.0f; + /* Set values from inputs */ + cu.spacemode = align_x; + cu.align_y = align_y; + cu.fsize = font_size; + cu.spacing = char_spacing; + cu.wordspace = word_spacing; + cu.linedist = line_spacing; + cu.vfont = vfont; + cu.overflow = overflow; + cu.tb = (TextBox *)MEM_calloc_arrayN(MAXTEXTBOX, sizeof(TextBox), __func__); + cu.tb->w = textbox_w; + cu.tb->h = textbox_h; + cu.totbox = 1; + size_t len_bytes; + size_t len_chars = BLI_strlen_utf8_ex(layout.text.c_str(), &len_bytes); + cu.len_char32 = len_chars; + cu.len = len_bytes; + cu.pos = len_chars; + /* The reason for the additional character here is unknown, but reflects other code elsewhere. */ + cu.str = (char *)MEM_mallocN(len_bytes + sizeof(char32_t), __func__); + cu.strinfo = (CharInfo *)MEM_callocN((len_chars + 1) * sizeof(CharInfo), __func__); + BLI_strncpy(cu.str, layout.text.c_str(), len_bytes + 1); + + struct CharTrans *chartransdata = nullptr; + int text_len; + bool text_free; + const char32_t *r_text = nullptr; + /* Mode FO_DUPLI used because it doesn't create curve splines. */ + BKE_vfont_to_curve_ex( + nullptr, &cu, FO_DUPLI, nullptr, &r_text, &text_len, &text_free, &chartransdata); + + if (text_free) { + MEM_freeN((void *)r_text); + } + + Span<CharInfo> info{cu.strinfo, text_len}; + layout.final_font_size = cu.fsize_realtime; + layout.positions.reserve(text_len); + + for (const int i : IndexRange(text_len)) { + CharTrans &ct = chartransdata[i]; + layout.positions.append(float2(ct.xof, ct.yof) * layout.final_font_size); + + if ((info[i].flag & CU_CHINFO_OVERFLOW) && (cu.overflow == CU_OVERFLOW_TRUNCATE)) { + const int offset = BLI_str_utf8_offset_from_index(layout.text.c_str(), i + 1); + layout.truncated_text = layout.text.substr(offset); + layout.text = layout.text.substr(0, offset); + break; + } + } + + MEM_SAFE_FREE(chartransdata); + MEM_SAFE_FREE(cu.str); + MEM_SAFE_FREE(cu.strinfo); + MEM_SAFE_FREE(cu.tb); + + return layout; +} + +/* Returns a mapping of UTF-32 character code to instance handle. */ +static Map<int, int> create_curve_instances(GeoNodeExecParams ¶ms, + const float fontsize, + const std::u32string &charcodes, + InstancesComponent &instance_component) +{ + VFont *vfont = (VFont *)params.node().id; + Map<int, int> handles; + + for (int i : IndexRange(charcodes.length())) { + if (handles.contains(charcodes[i])) { + continue; + } + Curve cu = {nullptr}; + cu.type = OB_FONT; + cu.resolu = 12; + cu.vfont = vfont; + CharInfo charinfo = {0}; + charinfo.mat_nr = 1; + + BKE_vfont_build_char(&cu, &cu.nurb, charcodes[i], &charinfo, 0, 0, 0, i, 1); + std::unique_ptr<CurveEval> curve_eval = curve_eval_from_dna_curve(cu); + BKE_nurbList_free(&cu.nurb); + float4x4 size_matrix = float4x4::identity(); + size_matrix.apply_scale(fontsize); + curve_eval->transform(size_matrix); + + GeometrySet geometry_set_curve = GeometrySet::create_with_curve(curve_eval.release()); + handles.add_new(charcodes[i], instance_component.add_reference(std::move(geometry_set_curve))); + } + return handles; +} + +static void add_instances_from_handles(InstancesComponent &instances, + const Map<int, int> &char_handles, + const std::u32string &charcodes, + const Span<float2> positions) +{ + instances.resize(positions.size()); + MutableSpan<int> handles = instances.instance_reference_handles(); + MutableSpan<float4x4> transforms = instances.instance_transforms(); + MutableSpan<int> instance_ids = instances.instance_ids(); + + threading::parallel_for(IndexRange(positions.size()), 256, [&](IndexRange range) { + for (const int i : range) { + handles[i] = char_handles.lookup(charcodes[i]); + transforms[i] = float4x4::from_location({positions[i].x, positions[i].y, 0}); + instance_ids[i] = i; + } + }); +} + +static void geo_node_string_to_curves_exec(GeoNodeExecParams params) +{ + TextLayout layout = get_text_layout(params); + + const NodeGeometryStringToCurves &storage = + *(const NodeGeometryStringToCurves *)params.node().storage; + if (storage.overflow == GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE) { + params.set_output("Remainder", std::move(layout.truncated_text)); + } + + if (layout.positions.size() == 0) { + params.set_output("Curves", GeometrySet()); + return; + } + + /* Convert UTF-8 encoded string to UTF-32. */ + std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter; + std::u32string utf32_text = converter.from_bytes(layout.text); + + /* Create and add instances. */ + GeometrySet geometry_set_out; + InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>(); + Map<int, int> char_handles = create_curve_instances( + params, layout.final_font_size, utf32_text, instances); + add_instances_from_handles(instances, char_handles, utf32_text, layout.positions); + + params.set_output("Curves", std::move(geometry_set_out)); +} + +} // namespace blender::nodes + +void register_node_type_geo_string_to_curves() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_STRING_TO_CURVES, "String to Curves", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_string_to_curves_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_string_to_curves_exec; + node_type_init(&ntype, blender::nodes::geo_node_string_to_curves_init); + node_type_update(&ntype, blender::nodes::geo_node_string_to_curves_update); + node_type_size(&ntype, 190, 120, 700); + node_type_storage(&ntype, + "NodeGeometryStringToCurves", + node_free_standard_storage, + node_copy_standard_storage); + ntype.draw_buttons = blender::nodes::geo_node_string_to_curves_layout; + nodeRegisterType(&ntype); +} |