1/**************************************************************************/
2/* text_paragraph.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "scene/resources/text_paragraph.h"
32
33void TextParagraph::_bind_methods() {
34 ClassDB::bind_method(D_METHOD("clear"), &TextParagraph::clear);
35
36 ClassDB::bind_method(D_METHOD("set_direction", "direction"), &TextParagraph::set_direction);
37 ClassDB::bind_method(D_METHOD("get_direction"), &TextParagraph::get_direction);
38
39 ADD_PROPERTY(PropertyInfo(Variant::INT, "direction", PROPERTY_HINT_ENUM, "Auto,Light-to-right,Right-to-left"), "set_direction", "get_direction");
40
41 ClassDB::bind_method(D_METHOD("set_custom_punctuation", "custom_punctuation"), &TextParagraph::set_custom_punctuation);
42 ClassDB::bind_method(D_METHOD("get_custom_punctuation"), &TextParagraph::get_custom_punctuation);
43
44 ADD_PROPERTY(PropertyInfo(Variant::STRING, "custom_punctuation"), "set_custom_punctuation", "get_custom_punctuation");
45
46 ClassDB::bind_method(D_METHOD("set_orientation", "orientation"), &TextParagraph::set_orientation);
47 ClassDB::bind_method(D_METHOD("get_orientation"), &TextParagraph::get_orientation);
48
49 ADD_PROPERTY(PropertyInfo(Variant::INT, "orientation", PROPERTY_HINT_ENUM, "Horizontal,Orientation"), "set_orientation", "get_orientation");
50
51 ClassDB::bind_method(D_METHOD("set_preserve_invalid", "enabled"), &TextParagraph::set_preserve_invalid);
52 ClassDB::bind_method(D_METHOD("get_preserve_invalid"), &TextParagraph::get_preserve_invalid);
53
54 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "preserve_invalid"), "set_preserve_invalid", "get_preserve_invalid");
55
56 ClassDB::bind_method(D_METHOD("set_preserve_control", "enabled"), &TextParagraph::set_preserve_control);
57 ClassDB::bind_method(D_METHOD("get_preserve_control"), &TextParagraph::get_preserve_control);
58
59 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "preserve_control"), "set_preserve_control", "get_preserve_control");
60
61 ClassDB::bind_method(D_METHOD("set_bidi_override", "override"), &TextParagraph::set_bidi_override);
62
63 ClassDB::bind_method(D_METHOD("set_dropcap", "text", "font", "font_size", "dropcap_margins", "language"), &TextParagraph::set_dropcap, DEFVAL(Rect2()), DEFVAL(""));
64 ClassDB::bind_method(D_METHOD("clear_dropcap"), &TextParagraph::clear_dropcap);
65
66 ClassDB::bind_method(D_METHOD("add_string", "text", "font", "font_size", "language", "meta"), &TextParagraph::add_string, DEFVAL(""), DEFVAL(Variant()));
67 ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length", "baseline"), &TextParagraph::add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1), DEFVAL(0.0));
68 ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align", "baseline"), &TextParagraph::resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(0.0));
69
70 ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &TextParagraph::set_alignment);
71 ClassDB::bind_method(D_METHOD("get_alignment"), &TextParagraph::get_alignment);
72
73 ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_alignment", "get_alignment");
74
75 ClassDB::bind_method(D_METHOD("tab_align", "tab_stops"), &TextParagraph::tab_align);
76
77 ClassDB::bind_method(D_METHOD("set_break_flags", "flags"), &TextParagraph::set_break_flags);
78 ClassDB::bind_method(D_METHOD("get_break_flags"), &TextParagraph::get_break_flags);
79
80 ADD_PROPERTY(PropertyInfo(Variant::INT, "break_flags", PROPERTY_HINT_FLAGS, "Mandatory,Word Bound,Grapheme Bound,Adaptive,Trim Spaces"), "set_break_flags", "get_break_flags");
81
82 ClassDB::bind_method(D_METHOD("set_justification_flags", "flags"), &TextParagraph::set_justification_flags);
83 ClassDB::bind_method(D_METHOD("get_justification_flags"), &TextParagraph::get_justification_flags);
84
85 ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification:1,Word Justification:2,Trim Edge Spaces After Justification:4,Justify Only After Last Tab:8,Constrain Ellipsis:16,Skip Last Line:32,Skip Last Line With Visible Characters:64,Do Not Skip Single Line:128"), "set_justification_flags", "get_justification_flags");
86
87 ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &TextParagraph::set_text_overrun_behavior);
88 ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &TextParagraph::get_text_overrun_behavior);
89
90 ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
91
92 ClassDB::bind_method(D_METHOD("set_width", "width"), &TextParagraph::set_width);
93 ClassDB::bind_method(D_METHOD("get_width"), &TextParagraph::get_width);
94
95 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "width"), "set_width", "get_width");
96
97 ClassDB::bind_method(D_METHOD("get_non_wrapped_size"), &TextParagraph::get_non_wrapped_size);
98 ClassDB::bind_method(D_METHOD("get_size"), &TextParagraph::get_size);
99
100 ClassDB::bind_method(D_METHOD("get_rid"), &TextParagraph::get_rid);
101 ClassDB::bind_method(D_METHOD("get_line_rid", "line"), &TextParagraph::get_line_rid);
102 ClassDB::bind_method(D_METHOD("get_dropcap_rid"), &TextParagraph::get_dropcap_rid);
103
104 ClassDB::bind_method(D_METHOD("get_line_count"), &TextParagraph::get_line_count);
105
106 ClassDB::bind_method(D_METHOD("set_max_lines_visible", "max_lines_visible"), &TextParagraph::set_max_lines_visible);
107 ClassDB::bind_method(D_METHOD("get_max_lines_visible"), &TextParagraph::get_max_lines_visible);
108
109 ADD_PROPERTY(PropertyInfo(Variant::INT, "max_lines_visible"), "set_max_lines_visible", "get_max_lines_visible");
110
111 ClassDB::bind_method(D_METHOD("get_line_objects", "line"), &TextParagraph::get_line_objects);
112 ClassDB::bind_method(D_METHOD("get_line_object_rect", "line", "key"), &TextParagraph::get_line_object_rect);
113 ClassDB::bind_method(D_METHOD("get_line_size", "line"), &TextParagraph::get_line_size);
114 ClassDB::bind_method(D_METHOD("get_line_range", "line"), &TextParagraph::get_line_range);
115 ClassDB::bind_method(D_METHOD("get_line_ascent", "line"), &TextParagraph::get_line_ascent);
116 ClassDB::bind_method(D_METHOD("get_line_descent", "line"), &TextParagraph::get_line_descent);
117 ClassDB::bind_method(D_METHOD("get_line_width", "line"), &TextParagraph::get_line_width);
118 ClassDB::bind_method(D_METHOD("get_line_underline_position", "line"), &TextParagraph::get_line_underline_position);
119 ClassDB::bind_method(D_METHOD("get_line_underline_thickness", "line"), &TextParagraph::get_line_underline_thickness);
120
121 ClassDB::bind_method(D_METHOD("get_dropcap_size"), &TextParagraph::get_dropcap_size);
122 ClassDB::bind_method(D_METHOD("get_dropcap_lines"), &TextParagraph::get_dropcap_lines);
123
124 ClassDB::bind_method(D_METHOD("draw", "canvas", "pos", "color", "dc_color"), &TextParagraph::draw, DEFVAL(Color(1, 1, 1)), DEFVAL(Color(1, 1, 1)));
125 ClassDB::bind_method(D_METHOD("draw_outline", "canvas", "pos", "outline_size", "color", "dc_color"), &TextParagraph::draw_outline, DEFVAL(1), DEFVAL(Color(1, 1, 1)), DEFVAL(Color(1, 1, 1)));
126
127 ClassDB::bind_method(D_METHOD("draw_line", "canvas", "pos", "line", "color"), &TextParagraph::draw_line, DEFVAL(Color(1, 1, 1)));
128 ClassDB::bind_method(D_METHOD("draw_line_outline", "canvas", "pos", "line", "outline_size", "color"), &TextParagraph::draw_line_outline, DEFVAL(1), DEFVAL(Color(1, 1, 1)));
129
130 ClassDB::bind_method(D_METHOD("draw_dropcap", "canvas", "pos", "color"), &TextParagraph::draw_dropcap, DEFVAL(Color(1, 1, 1)));
131 ClassDB::bind_method(D_METHOD("draw_dropcap_outline", "canvas", "pos", "outline_size", "color"), &TextParagraph::draw_dropcap_outline, DEFVAL(1), DEFVAL(Color(1, 1, 1)));
132
133 ClassDB::bind_method(D_METHOD("hit_test", "coords"), &TextParagraph::hit_test);
134}
135
136void TextParagraph::_shape_lines() {
137 // When a shaped text is invalidated by an external source, we want to reshape it.
138 if (!TS->shaped_text_is_ready(rid) || !TS->shaped_text_is_ready(dropcap_rid)) {
139 lines_dirty = true;
140 }
141
142 for (const RID &line_rid : lines_rid) {
143 if (!TS->shaped_text_is_ready(line_rid)) {
144 lines_dirty = true;
145 break;
146 }
147 }
148
149 if (lines_dirty) {
150 for (const RID &line_rid : lines_rid) {
151 TS->free_rid(line_rid);
152 }
153 lines_rid.clear();
154
155 if (!tab_stops.is_empty()) {
156 TS->shaped_text_tab_align(rid, tab_stops);
157 }
158
159 float h_offset = 0.f;
160 float v_offset = 0.f;
161 int start = 0;
162 dropcap_lines = 0;
163
164 if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
165 h_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x;
166 v_offset = TS->shaped_text_get_size(dropcap_rid).y + dropcap_margins.size.y + dropcap_margins.position.y;
167 } else {
168 h_offset = TS->shaped_text_get_size(dropcap_rid).y + dropcap_margins.size.y + dropcap_margins.position.y;
169 v_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x;
170 }
171
172 if (h_offset > 0) {
173 // Dropcap, flow around.
174 PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width - h_offset, 0, brk_flags);
175 for (int i = 0; i < line_breaks.size(); i = i + 2) {
176 RID line = TS->shaped_text_substr(rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]);
177 float h = (TS->shaped_text_get_orientation(line) == TextServer::ORIENTATION_HORIZONTAL) ? TS->shaped_text_get_size(line).y : TS->shaped_text_get_size(line).x;
178 if (v_offset < h) {
179 TS->free_rid(line);
180 break;
181 }
182 if (!tab_stops.is_empty()) {
183 TS->shaped_text_tab_align(line, tab_stops);
184 }
185 dropcap_lines++;
186 v_offset -= h;
187 start = line_breaks[i + 1];
188 lines_rid.push_back(line);
189 }
190 }
191 // Use fixed for the rest of lines.
192 PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width, start, brk_flags);
193 for (int i = 0; i < line_breaks.size(); i = i + 2) {
194 RID line = TS->shaped_text_substr(rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]);
195 if (!tab_stops.is_empty()) {
196 TS->shaped_text_tab_align(line, tab_stops);
197 }
198 lines_rid.push_back(line);
199 }
200
201 BitField<TextServer::TextOverrunFlag> overrun_flags = TextServer::OVERRUN_NO_TRIM;
202 if (overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) {
203 switch (overrun_behavior) {
204 case TextServer::OVERRUN_TRIM_WORD_ELLIPSIS:
205 overrun_flags.set_flag(TextServer::OVERRUN_TRIM);
206 overrun_flags.set_flag(TextServer::OVERRUN_TRIM_WORD_ONLY);
207 overrun_flags.set_flag(TextServer::OVERRUN_ADD_ELLIPSIS);
208 break;
209 case TextServer::OVERRUN_TRIM_ELLIPSIS:
210 overrun_flags.set_flag(TextServer::OVERRUN_TRIM);
211 overrun_flags.set_flag(TextServer::OVERRUN_ADD_ELLIPSIS);
212 break;
213 case TextServer::OVERRUN_TRIM_WORD:
214 overrun_flags.set_flag(TextServer::OVERRUN_TRIM);
215 overrun_flags.set_flag(TextServer::OVERRUN_TRIM_WORD_ONLY);
216 break;
217 case TextServer::OVERRUN_TRIM_CHAR:
218 overrun_flags.set_flag(TextServer::OVERRUN_TRIM);
219 break;
220 case TextServer::OVERRUN_NO_TRIMMING:
221 break;
222 }
223 }
224
225 bool autowrap_enabled = brk_flags.has_flag(TextServer::BREAK_WORD_BOUND) || brk_flags.has_flag(TextServer::BREAK_GRAPHEME_BOUND);
226
227 // Fill after min_size calculation.
228 if (autowrap_enabled) {
229 int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, (int)lines_rid.size()) : (int)lines_rid.size();
230 bool lines_hidden = visible_lines > 0 && visible_lines < (int)lines_rid.size();
231 if (lines_hidden) {
232 overrun_flags.set_flag(TextServer::OVERRUN_ENFORCE_ELLIPSIS);
233 }
234 if (alignment == HORIZONTAL_ALIGNMENT_FILL) {
235 int jst_to_line = visible_lines;
236 if (lines_rid.size() == 1 && jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) {
237 jst_to_line = lines_rid.size();
238 } else {
239 if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE)) {
240 jst_to_line = visible_lines - 1;
241 }
242 if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS)) {
243 for (int i = visible_lines - 1; i >= 0; i--) {
244 if (TS->shaped_text_has_visible_chars(lines_rid[i])) {
245 jst_to_line = i;
246 break;
247 }
248 }
249 }
250 }
251 for (int i = 0; i < (int)lines_rid.size(); i++) {
252 float line_w = (i <= dropcap_lines) ? (width - h_offset) : width;
253 if (i < jst_to_line) {
254 TS->shaped_text_fit_to_width(lines_rid[i], line_w, jst_flags);
255 } else if (i == (visible_lines - 1)) {
256 TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], line_w, overrun_flags);
257 }
258 }
259 } else if (lines_hidden) {
260 TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], (visible_lines - 1 <= dropcap_lines) ? (width - h_offset) : width, overrun_flags);
261 }
262 } else {
263 // Autowrap disabled.
264 int jst_to_line = lines_rid.size();
265 if (lines_rid.size() == 1 && jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) {
266 jst_to_line = lines_rid.size();
267 } else {
268 if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE)) {
269 jst_to_line = lines_rid.size() - 1;
270 }
271 if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS)) {
272 for (int i = lines_rid.size() - 1; i >= 0; i--) {
273 if (TS->shaped_text_has_visible_chars(lines_rid[i])) {
274 jst_to_line = i;
275 break;
276 }
277 }
278 }
279 }
280 for (int i = 0; i < (int)lines_rid.size(); i++) {
281 float line_w = (i <= dropcap_lines) ? (width - h_offset) : width;
282 if (i < jst_to_line && alignment == HORIZONTAL_ALIGNMENT_FILL) {
283 TS->shaped_text_fit_to_width(lines_rid[i], line_w, jst_flags);
284 overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE);
285 TS->shaped_text_overrun_trim_to_width(lines_rid[i], line_w, overrun_flags);
286 TS->shaped_text_fit_to_width(lines_rid[i], line_w, jst_flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS);
287 } else {
288 TS->shaped_text_overrun_trim_to_width(lines_rid[i], line_w, overrun_flags);
289 }
290 }
291 }
292 lines_dirty = false;
293 }
294}
295
296RID TextParagraph::get_rid() const {
297 return rid;
298}
299
300RID TextParagraph::get_line_rid(int p_line) const {
301 _THREAD_SAFE_METHOD_
302
303 const_cast<TextParagraph *>(this)->_shape_lines();
304 ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), RID());
305 return lines_rid[p_line];
306}
307
308RID TextParagraph::get_dropcap_rid() const {
309 return dropcap_rid;
310}
311
312void TextParagraph::clear() {
313 _THREAD_SAFE_METHOD_
314
315 for (const RID &line_rid : lines_rid) {
316 TS->free_rid(line_rid);
317 }
318 lines_rid.clear();
319 TS->shaped_text_clear(rid);
320 TS->shaped_text_clear(dropcap_rid);
321}
322
323void TextParagraph::set_preserve_invalid(bool p_enabled) {
324 _THREAD_SAFE_METHOD_
325
326 TS->shaped_text_set_preserve_invalid(rid, p_enabled);
327 TS->shaped_text_set_preserve_invalid(dropcap_rid, p_enabled);
328 lines_dirty = true;
329}
330
331bool TextParagraph::get_preserve_invalid() const {
332 _THREAD_SAFE_METHOD_
333
334 return TS->shaped_text_get_preserve_invalid(rid);
335}
336
337void TextParagraph::set_preserve_control(bool p_enabled) {
338 _THREAD_SAFE_METHOD_
339
340 TS->shaped_text_set_preserve_control(rid, p_enabled);
341 TS->shaped_text_set_preserve_control(dropcap_rid, p_enabled);
342 lines_dirty = true;
343}
344
345bool TextParagraph::get_preserve_control() const {
346 _THREAD_SAFE_METHOD_
347
348 return TS->shaped_text_get_preserve_control(rid);
349}
350
351void TextParagraph::set_direction(TextServer::Direction p_direction) {
352 _THREAD_SAFE_METHOD_
353
354 TS->shaped_text_set_direction(rid, p_direction);
355 TS->shaped_text_set_direction(dropcap_rid, p_direction);
356 lines_dirty = true;
357}
358
359TextServer::Direction TextParagraph::get_direction() const {
360 _THREAD_SAFE_METHOD_
361
362 const_cast<TextParagraph *>(this)->_shape_lines();
363 return TS->shaped_text_get_direction(rid);
364}
365
366void TextParagraph::set_custom_punctuation(const String &p_punct) {
367 _THREAD_SAFE_METHOD_
368
369 TS->shaped_text_set_custom_punctuation(rid, p_punct);
370 lines_dirty = true;
371}
372
373String TextParagraph::get_custom_punctuation() const {
374 _THREAD_SAFE_METHOD_
375
376 return TS->shaped_text_get_custom_punctuation(rid);
377}
378
379void TextParagraph::set_orientation(TextServer::Orientation p_orientation) {
380 _THREAD_SAFE_METHOD_
381
382 TS->shaped_text_set_orientation(rid, p_orientation);
383 TS->shaped_text_set_orientation(dropcap_rid, p_orientation);
384 lines_dirty = true;
385}
386
387TextServer::Orientation TextParagraph::get_orientation() const {
388 _THREAD_SAFE_METHOD_
389
390 const_cast<TextParagraph *>(this)->_shape_lines();
391 return TS->shaped_text_get_orientation(rid);
392}
393
394bool TextParagraph::set_dropcap(const String &p_text, const Ref<Font> &p_font, int p_font_size, const Rect2 &p_dropcap_margins, const String &p_language) {
395 _THREAD_SAFE_METHOD_
396 ERR_FAIL_COND_V(p_font.is_null(), false);
397 TS->shaped_text_clear(dropcap_rid);
398 dropcap_margins = p_dropcap_margins;
399 bool res = TS->shaped_text_add_string(dropcap_rid, p_text, p_font->get_rids(), p_font_size, p_font->get_opentype_features(), p_language);
400 for (int i = 0; i < TextServer::SPACING_MAX; i++) {
401 TS->shaped_text_set_spacing(dropcap_rid, TextServer::SpacingType(i), p_font->get_spacing(TextServer::SpacingType(i)));
402 }
403 lines_dirty = true;
404 return res;
405}
406
407void TextParagraph::clear_dropcap() {
408 _THREAD_SAFE_METHOD_
409 dropcap_margins = Rect2();
410 TS->shaped_text_clear(dropcap_rid);
411 lines_dirty = true;
412}
413
414bool TextParagraph::add_string(const String &p_text, const Ref<Font> &p_font, int p_font_size, const String &p_language, const Variant &p_meta) {
415 _THREAD_SAFE_METHOD_
416 ERR_FAIL_COND_V(p_font.is_null(), false);
417 bool res = TS->shaped_text_add_string(rid, p_text, p_font->get_rids(), p_font_size, p_font->get_opentype_features(), p_language, p_meta);
418 for (int i = 0; i < TextServer::SPACING_MAX; i++) {
419 TS->shaped_text_set_spacing(rid, TextServer::SpacingType(i), p_font->get_spacing(TextServer::SpacingType(i)));
420 }
421 lines_dirty = true;
422 return res;
423}
424
425void TextParagraph::set_bidi_override(const Array &p_override) {
426 _THREAD_SAFE_METHOD_
427
428 TS->shaped_text_set_bidi_override(rid, p_override);
429 lines_dirty = true;
430}
431
432bool TextParagraph::add_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align, int p_length, float p_baseline) {
433 _THREAD_SAFE_METHOD_
434
435 bool res = TS->shaped_text_add_object(rid, p_key, p_size, p_inline_align, p_length, p_baseline);
436 lines_dirty = true;
437 return res;
438}
439
440bool TextParagraph::resize_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align, float p_baseline) {
441 _THREAD_SAFE_METHOD_
442
443 bool res = TS->shaped_text_resize_object(rid, p_key, p_size, p_inline_align, p_baseline);
444 lines_dirty = true;
445 return res;
446}
447
448void TextParagraph::set_alignment(HorizontalAlignment p_alignment) {
449 _THREAD_SAFE_METHOD_
450
451 if (alignment != p_alignment) {
452 if (alignment == HORIZONTAL_ALIGNMENT_FILL || p_alignment == HORIZONTAL_ALIGNMENT_FILL) {
453 alignment = p_alignment;
454 lines_dirty = true;
455 } else {
456 alignment = p_alignment;
457 }
458 }
459}
460
461HorizontalAlignment TextParagraph::get_alignment() const {
462 return alignment;
463}
464
465void TextParagraph::tab_align(const Vector<float> &p_tab_stops) {
466 _THREAD_SAFE_METHOD_
467
468 tab_stops = p_tab_stops;
469 lines_dirty = true;
470}
471
472void TextParagraph::set_justification_flags(BitField<TextServer::JustificationFlag> p_flags) {
473 _THREAD_SAFE_METHOD_
474
475 if (jst_flags != p_flags) {
476 jst_flags = p_flags;
477 lines_dirty = true;
478 }
479}
480
481BitField<TextServer::JustificationFlag> TextParagraph::get_justification_flags() const {
482 return jst_flags;
483}
484
485void TextParagraph::set_break_flags(BitField<TextServer::LineBreakFlag> p_flags) {
486 _THREAD_SAFE_METHOD_
487
488 if (brk_flags != p_flags) {
489 brk_flags = p_flags;
490 lines_dirty = true;
491 }
492}
493
494BitField<TextServer::LineBreakFlag> TextParagraph::get_break_flags() const {
495 return brk_flags;
496}
497
498void TextParagraph::set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior) {
499 _THREAD_SAFE_METHOD_
500
501 if (overrun_behavior != p_behavior) {
502 overrun_behavior = p_behavior;
503 lines_dirty = true;
504 }
505}
506
507TextServer::OverrunBehavior TextParagraph::get_text_overrun_behavior() const {
508 return overrun_behavior;
509}
510
511void TextParagraph::set_width(float p_width) {
512 _THREAD_SAFE_METHOD_
513
514 if (width != p_width) {
515 width = p_width;
516 lines_dirty = true;
517 }
518}
519
520float TextParagraph::get_width() const {
521 return width;
522}
523
524Size2 TextParagraph::get_non_wrapped_size() const {
525 _THREAD_SAFE_METHOD_
526
527 const_cast<TextParagraph *>(this)->_shape_lines();
528 return TS->shaped_text_get_size(rid);
529}
530
531Size2 TextParagraph::get_size() const {
532 _THREAD_SAFE_METHOD_
533
534 const_cast<TextParagraph *>(this)->_shape_lines();
535 Size2 size;
536 int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, (int)lines_rid.size()) : (int)lines_rid.size();
537 for (int i = 0; i < visible_lines; i++) {
538 Size2 lsize = TS->shaped_text_get_size(lines_rid[i]);
539 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
540 size.x = MAX(size.x, lsize.x);
541 size.y += lsize.y;
542 } else {
543 size.x += lsize.x;
544 size.y = MAX(size.y, lsize.y);
545 }
546 }
547 return size;
548}
549
550int TextParagraph::get_line_count() const {
551 _THREAD_SAFE_METHOD_
552
553 const_cast<TextParagraph *>(this)->_shape_lines();
554 return (int)lines_rid.size();
555}
556
557void TextParagraph::set_max_lines_visible(int p_lines) {
558 _THREAD_SAFE_METHOD_
559
560 if (p_lines != max_lines_visible) {
561 max_lines_visible = p_lines;
562 lines_dirty = true;
563 }
564}
565
566int TextParagraph::get_max_lines_visible() const {
567 return max_lines_visible;
568}
569
570Array TextParagraph::get_line_objects(int p_line) const {
571 _THREAD_SAFE_METHOD_
572
573 const_cast<TextParagraph *>(this)->_shape_lines();
574 ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), Array());
575 return TS->shaped_text_get_objects(lines_rid[p_line]);
576}
577
578Rect2 TextParagraph::get_line_object_rect(int p_line, Variant p_key) const {
579 _THREAD_SAFE_METHOD_
580
581 const_cast<TextParagraph *>(this)->_shape_lines();
582 ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), Rect2());
583
584 Vector2 ofs;
585
586 float h_offset = 0.f;
587 if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
588 h_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x;
589 } else {
590 h_offset = TS->shaped_text_get_size(dropcap_rid).y + dropcap_margins.size.y + dropcap_margins.position.y;
591 }
592
593 for (int i = 0; i <= p_line; i++) {
594 float l_width = width;
595 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
596 ofs.x = 0.f;
597 ofs.y += TS->shaped_text_get_ascent(lines_rid[i]);
598 if (i <= dropcap_lines) {
599 if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
600 ofs.x -= h_offset;
601 }
602 l_width -= h_offset;
603 }
604 } else {
605 ofs.y = 0.f;
606 ofs.x += TS->shaped_text_get_ascent(lines_rid[i]);
607 if (i <= dropcap_lines) {
608 if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
609 ofs.x -= h_offset;
610 }
611 l_width -= h_offset;
612 }
613 }
614 float length = TS->shaped_text_get_width(lines_rid[i]);
615 if (width > 0) {
616 switch (alignment) {
617 case HORIZONTAL_ALIGNMENT_FILL:
618 if (TS->shaped_text_get_inferred_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) {
619 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
620 ofs.x += l_width - length;
621 } else {
622 ofs.y += l_width - length;
623 }
624 }
625 break;
626 case HORIZONTAL_ALIGNMENT_LEFT:
627 break;
628 case HORIZONTAL_ALIGNMENT_CENTER: {
629 if (length <= l_width) {
630 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
631 ofs.x += Math::floor((l_width - length) / 2.0);
632 } else {
633 ofs.y += Math::floor((l_width - length) / 2.0);
634 }
635 } else if (TS->shaped_text_get_inferred_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) {
636 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
637 ofs.x += l_width - length;
638 } else {
639 ofs.y += l_width - length;
640 }
641 }
642 } break;
643 case HORIZONTAL_ALIGNMENT_RIGHT: {
644 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
645 ofs.x += l_width - length;
646 } else {
647 ofs.y += l_width - length;
648 }
649 } break;
650 }
651 }
652 if (i != p_line) {
653 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
654 ofs.x = 0.f;
655 ofs.y += TS->shaped_text_get_descent(lines_rid[i]);
656 } else {
657 ofs.y = 0.f;
658 ofs.x += TS->shaped_text_get_descent(lines_rid[i]);
659 }
660 }
661 }
662
663 Rect2 rect = TS->shaped_text_get_object_rect(lines_rid[p_line], p_key);
664 rect.position += ofs;
665
666 return rect;
667}
668
669Size2 TextParagraph::get_line_size(int p_line) const {
670 _THREAD_SAFE_METHOD_
671
672 const_cast<TextParagraph *>(this)->_shape_lines();
673 ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), Size2());
674 return TS->shaped_text_get_size(lines_rid[p_line]);
675}
676
677Vector2i TextParagraph::get_line_range(int p_line) const {
678 _THREAD_SAFE_METHOD_
679
680 const_cast<TextParagraph *>(this)->_shape_lines();
681 ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), Vector2i());
682 return TS->shaped_text_get_range(lines_rid[p_line]);
683}
684
685float TextParagraph::get_line_ascent(int p_line) const {
686 _THREAD_SAFE_METHOD_
687
688 const_cast<TextParagraph *>(this)->_shape_lines();
689 ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), 0.f);
690 return TS->shaped_text_get_ascent(lines_rid[p_line]);
691}
692
693float TextParagraph::get_line_descent(int p_line) const {
694 _THREAD_SAFE_METHOD_
695
696 const_cast<TextParagraph *>(this)->_shape_lines();
697 ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), 0.f);
698 return TS->shaped_text_get_descent(lines_rid[p_line]);
699}
700
701float TextParagraph::get_line_width(int p_line) const {
702 _THREAD_SAFE_METHOD_
703
704 const_cast<TextParagraph *>(this)->_shape_lines();
705 ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), 0.f);
706 return TS->shaped_text_get_width(lines_rid[p_line]);
707}
708
709float TextParagraph::get_line_underline_position(int p_line) const {
710 _THREAD_SAFE_METHOD_
711
712 const_cast<TextParagraph *>(this)->_shape_lines();
713 ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), 0.f);
714 return TS->shaped_text_get_underline_position(lines_rid[p_line]);
715}
716
717float TextParagraph::get_line_underline_thickness(int p_line) const {
718 _THREAD_SAFE_METHOD_
719
720 const_cast<TextParagraph *>(this)->_shape_lines();
721 ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), 0.f);
722 return TS->shaped_text_get_underline_thickness(lines_rid[p_line]);
723}
724
725Size2 TextParagraph::get_dropcap_size() const {
726 _THREAD_SAFE_METHOD_
727
728 return TS->shaped_text_get_size(dropcap_rid) + dropcap_margins.size + dropcap_margins.position;
729}
730
731int TextParagraph::get_dropcap_lines() const {
732 return dropcap_lines;
733}
734
735void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_color, const Color &p_dc_color) const {
736 _THREAD_SAFE_METHOD_
737
738 const_cast<TextParagraph *>(this)->_shape_lines();
739 Vector2 ofs = p_pos;
740 float h_offset = 0.f;
741 if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
742 h_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x;
743 } else {
744 h_offset = TS->shaped_text_get_size(dropcap_rid).y + dropcap_margins.size.y + dropcap_margins.position.y;
745 }
746
747 if (h_offset > 0) {
748 // Draw dropcap.
749 Vector2 dc_off = ofs;
750 if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_RTL) {
751 if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
752 dc_off.x += width - h_offset;
753 } else {
754 dc_off.y += width - h_offset;
755 }
756 }
757 TS->shaped_text_draw(dropcap_rid, p_canvas, dc_off + Vector2(0, TS->shaped_text_get_ascent(dropcap_rid) + dropcap_margins.size.y + dropcap_margins.position.y / 2), -1, -1, p_dc_color);
758 }
759
760 int lines_visible = (max_lines_visible >= 0) ? MIN(max_lines_visible, (int)lines_rid.size()) : (int)lines_rid.size();
761
762 for (int i = 0; i < lines_visible; i++) {
763 float l_width = width;
764 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
765 ofs.x = p_pos.x;
766 ofs.y += TS->shaped_text_get_ascent(lines_rid[i]);
767 if (i <= dropcap_lines) {
768 if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
769 ofs.x -= h_offset;
770 }
771 l_width -= h_offset;
772 }
773 } else {
774 ofs.y = p_pos.y;
775 ofs.x += TS->shaped_text_get_ascent(lines_rid[i]);
776 if (i <= dropcap_lines) {
777 if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
778 ofs.x -= h_offset;
779 }
780 l_width -= h_offset;
781 }
782 }
783 float line_width = TS->shaped_text_get_width(lines_rid[i]);
784 if (width > 0) {
785 switch (alignment) {
786 case HORIZONTAL_ALIGNMENT_FILL:
787 if (TS->shaped_text_get_inferred_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) {
788 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
789 ofs.x += l_width - line_width;
790 } else {
791 ofs.y += l_width - line_width;
792 }
793 }
794 break;
795 case HORIZONTAL_ALIGNMENT_LEFT:
796 break;
797 case HORIZONTAL_ALIGNMENT_CENTER: {
798 if (line_width <= l_width) {
799 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
800 ofs.x += Math::floor((l_width - line_width) / 2.0);
801 } else {
802 ofs.y += Math::floor((l_width - line_width) / 2.0);
803 }
804 } else if (TS->shaped_text_get_inferred_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) {
805 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
806 ofs.x += l_width - line_width;
807 } else {
808 ofs.y += l_width - line_width;
809 }
810 }
811 } break;
812 case HORIZONTAL_ALIGNMENT_RIGHT: {
813 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
814 ofs.x += l_width - line_width;
815 } else {
816 ofs.y += l_width - line_width;
817 }
818 } break;
819 }
820 }
821 float clip_l;
822 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
823 clip_l = MAX(0, p_pos.x - ofs.x);
824 } else {
825 clip_l = MAX(0, p_pos.y - ofs.y);
826 }
827 TS->shaped_text_draw(lines_rid[i], p_canvas, ofs, clip_l, clip_l + l_width, p_color);
828 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
829 ofs.x = p_pos.x;
830 ofs.y += TS->shaped_text_get_descent(lines_rid[i]);
831 } else {
832 ofs.y = p_pos.y;
833 ofs.x += TS->shaped_text_get_descent(lines_rid[i]);
834 }
835 }
836}
837
838void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outline_size, const Color &p_color, const Color &p_dc_color) const {
839 _THREAD_SAFE_METHOD_
840
841 const_cast<TextParagraph *>(this)->_shape_lines();
842 Vector2 ofs = p_pos;
843
844 float h_offset = 0.f;
845 if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
846 h_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x;
847 } else {
848 h_offset = TS->shaped_text_get_size(dropcap_rid).y + dropcap_margins.size.y + dropcap_margins.position.y;
849 }
850
851 if (h_offset > 0) {
852 // Draw dropcap.
853 Vector2 dc_off = ofs;
854 if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_RTL) {
855 if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
856 dc_off.x += width - h_offset;
857 } else {
858 dc_off.y += width - h_offset;
859 }
860 }
861 TS->shaped_text_draw_outline(dropcap_rid, p_canvas, dc_off + Vector2(dropcap_margins.position.x, TS->shaped_text_get_ascent(dropcap_rid) + dropcap_margins.position.y), -1, -1, p_outline_size, p_dc_color);
862 }
863
864 for (int i = 0; i < (int)lines_rid.size(); i++) {
865 float l_width = width;
866 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
867 ofs.x = p_pos.x;
868 ofs.y += TS->shaped_text_get_ascent(lines_rid[i]);
869 if (i <= dropcap_lines) {
870 if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
871 ofs.x -= h_offset;
872 }
873 l_width -= h_offset;
874 }
875 } else {
876 ofs.y = p_pos.y;
877 ofs.x += TS->shaped_text_get_ascent(lines_rid[i]);
878 if (i <= dropcap_lines) {
879 if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
880 ofs.x -= h_offset;
881 }
882 l_width -= h_offset;
883 }
884 }
885 float length = TS->shaped_text_get_width(lines_rid[i]);
886 if (width > 0) {
887 switch (alignment) {
888 case HORIZONTAL_ALIGNMENT_FILL:
889 if (TS->shaped_text_get_inferred_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) {
890 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
891 ofs.x += l_width - length;
892 } else {
893 ofs.y += l_width - length;
894 }
895 }
896 break;
897 case HORIZONTAL_ALIGNMENT_LEFT:
898 break;
899 case HORIZONTAL_ALIGNMENT_CENTER: {
900 if (length <= l_width) {
901 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
902 ofs.x += Math::floor((l_width - length) / 2.0);
903 } else {
904 ofs.y += Math::floor((l_width - length) / 2.0);
905 }
906 } else if (TS->shaped_text_get_inferred_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) {
907 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
908 ofs.x += l_width - length;
909 } else {
910 ofs.y += l_width - length;
911 }
912 }
913 } break;
914 case HORIZONTAL_ALIGNMENT_RIGHT: {
915 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
916 ofs.x += l_width - length;
917 } else {
918 ofs.y += l_width - length;
919 }
920 } break;
921 }
922 }
923 float clip_l;
924 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
925 clip_l = MAX(0, p_pos.x - ofs.x);
926 } else {
927 clip_l = MAX(0, p_pos.y - ofs.y);
928 }
929 TS->shaped_text_draw_outline(lines_rid[i], p_canvas, ofs, clip_l, clip_l + l_width, p_outline_size, p_color);
930 if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
931 ofs.x = p_pos.x;
932 ofs.y += TS->shaped_text_get_descent(lines_rid[i]);
933 } else {
934 ofs.y = p_pos.y;
935 ofs.x += TS->shaped_text_get_descent(lines_rid[i]);
936 }
937 }
938}
939
940int TextParagraph::hit_test(const Point2 &p_coords) const {
941 _THREAD_SAFE_METHOD_
942
943 const_cast<TextParagraph *>(this)->_shape_lines();
944 Vector2 ofs;
945 if (TS->shaped_text_get_orientation(rid) == TextServer::ORIENTATION_HORIZONTAL) {
946 if (ofs.y < 0) {
947 return 0;
948 }
949 } else {
950 if (ofs.x < 0) {
951 return 0;
952 }
953 }
954 for (const RID &line_rid : lines_rid) {
955 if (TS->shaped_text_get_orientation(line_rid) == TextServer::ORIENTATION_HORIZONTAL) {
956 if ((p_coords.y >= ofs.y) && (p_coords.y <= ofs.y + TS->shaped_text_get_size(line_rid).y)) {
957 return TS->shaped_text_hit_test_position(line_rid, p_coords.x);
958 }
959 ofs.y += TS->shaped_text_get_size(line_rid).y;
960 } else {
961 if ((p_coords.x >= ofs.x) && (p_coords.x <= ofs.x + TS->shaped_text_get_size(line_rid).x)) {
962 return TS->shaped_text_hit_test_position(line_rid, p_coords.y);
963 }
964 ofs.y += TS->shaped_text_get_size(line_rid).x;
965 }
966 }
967 return TS->shaped_text_get_range(rid).y;
968}
969
970void TextParagraph::draw_dropcap(RID p_canvas, const Vector2 &p_pos, const Color &p_color) const {
971 _THREAD_SAFE_METHOD_
972
973 Vector2 ofs = p_pos;
974 float h_offset = 0.f;
975 if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
976 h_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x;
977 } else {
978 h_offset = TS->shaped_text_get_size(dropcap_rid).y + dropcap_margins.size.y + dropcap_margins.position.y;
979 }
980
981 if (h_offset > 0) {
982 // Draw dropcap.
983 if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_RTL) {
984 if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
985 ofs.x += width - h_offset;
986 } else {
987 ofs.y += width - h_offset;
988 }
989 }
990 TS->shaped_text_draw(dropcap_rid, p_canvas, ofs + Vector2(dropcap_margins.position.x, TS->shaped_text_get_ascent(dropcap_rid) + dropcap_margins.position.y), -1, -1, p_color);
991 }
992}
993
994void TextParagraph::draw_dropcap_outline(RID p_canvas, const Vector2 &p_pos, int p_outline_size, const Color &p_color) const {
995 _THREAD_SAFE_METHOD_
996
997 Vector2 ofs = p_pos;
998 float h_offset = 0.f;
999 if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
1000 h_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x;
1001 } else {
1002 h_offset = TS->shaped_text_get_size(dropcap_rid).y + dropcap_margins.size.y + dropcap_margins.position.y;
1003 }
1004
1005 if (h_offset > 0) {
1006 // Draw dropcap.
1007 if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_RTL) {
1008 if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
1009 ofs.x += width - h_offset;
1010 } else {
1011 ofs.y += width - h_offset;
1012 }
1013 }
1014 TS->shaped_text_draw_outline(dropcap_rid, p_canvas, ofs + Vector2(dropcap_margins.position.x, TS->shaped_text_get_ascent(dropcap_rid) + dropcap_margins.position.y), -1, -1, p_outline_size, p_color);
1015 }
1016}
1017
1018void TextParagraph::draw_line(RID p_canvas, const Vector2 &p_pos, int p_line, const Color &p_color) const {
1019 _THREAD_SAFE_METHOD_
1020
1021 const_cast<TextParagraph *>(this)->_shape_lines();
1022 ERR_FAIL_COND(p_line < 0 || p_line >= (int)lines_rid.size());
1023
1024 Vector2 ofs = p_pos;
1025
1026 if (TS->shaped_text_get_orientation(lines_rid[p_line]) == TextServer::ORIENTATION_HORIZONTAL) {
1027 ofs.y += TS->shaped_text_get_ascent(lines_rid[p_line]);
1028 } else {
1029 ofs.x += TS->shaped_text_get_ascent(lines_rid[p_line]);
1030 }
1031 return TS->shaped_text_draw(lines_rid[p_line], p_canvas, ofs, -1, -1, p_color);
1032}
1033
1034void TextParagraph::draw_line_outline(RID p_canvas, const Vector2 &p_pos, int p_line, int p_outline_size, const Color &p_color) const {
1035 _THREAD_SAFE_METHOD_
1036
1037 const_cast<TextParagraph *>(this)->_shape_lines();
1038 ERR_FAIL_COND(p_line < 0 || p_line >= (int)lines_rid.size());
1039
1040 Vector2 ofs = p_pos;
1041 if (TS->shaped_text_get_orientation(lines_rid[p_line]) == TextServer::ORIENTATION_HORIZONTAL) {
1042 ofs.y += TS->shaped_text_get_ascent(lines_rid[p_line]);
1043 } else {
1044 ofs.x += TS->shaped_text_get_ascent(lines_rid[p_line]);
1045 }
1046 return TS->shaped_text_draw_outline(lines_rid[p_line], p_canvas, ofs, -1, -1, p_outline_size, p_color);
1047}
1048
1049TextParagraph::TextParagraph(const String &p_text, const Ref<Font> &p_font, int p_font_size, const String &p_language, float p_width, TextServer::Direction p_direction, TextServer::Orientation p_orientation) {
1050 rid = TS->create_shaped_text(p_direction, p_orientation);
1051 if (p_font.is_valid()) {
1052 TS->shaped_text_add_string(rid, p_text, p_font->get_rids(), p_font_size, p_font->get_opentype_features(), p_language);
1053 for (int i = 0; i < TextServer::SPACING_MAX; i++) {
1054 TS->shaped_text_set_spacing(rid, TextServer::SpacingType(i), p_font->get_spacing(TextServer::SpacingType(i)));
1055 }
1056 }
1057 width = p_width;
1058}
1059
1060TextParagraph::TextParagraph() {
1061 rid = TS->create_shaped_text();
1062 dropcap_rid = TS->create_shaped_text();
1063}
1064
1065TextParagraph::~TextParagraph() {
1066 for (const RID &line_rid : lines_rid) {
1067 TS->free_rid(line_rid);
1068 }
1069 lines_rid.clear();
1070 TS->free_rid(rid);
1071 TS->free_rid(dropcap_rid);
1072}
1073