diff options
author | Clément Foucault <foucault.clem@gmail.com> | 2022-01-26 22:25:16 +0300 |
---|---|---|
committer | Clément Foucault <foucault.clem@gmail.com> | 2022-01-26 22:27:16 +0300 |
commit | af87b6d8cb75d9d625378dee25d726a0d55f75c6 (patch) | |
tree | b400a9a7e8d63dc2e60c45e988e969798c066d3b /source/blender/blenkernel/intern/vfont.c | |
parent | 42d2c96d4cceab46de8eebf30497e7399a2998e0 (diff) | |
parent | 57dfec79f4ab1ceeb8c5f6049aa03e779e7871c0 (diff) |
Merge branch 'master' into draw-viewport-data
# Conflicts:
# source/blender/draw/DRW_engine_types.h
# source/blender/draw/intern/draw_manager.c
# source/blender/draw/intern/draw_manager.h
# source/blender/draw/intern/draw_manager_profiling.c
# source/blender/draw/intern/draw_manager_text.h
# source/blender/draw/intern/draw_texture_pool.cc
# source/blender/draw/intern/draw_texture_pool.h
# source/blender/draw/intern/draw_view_data.cc
# source/blender/draw/intern/draw_view_data.h
# source/blender/editors/space_view3d/view3d_draw.c
# source/blender/gpu/GPU_texture.h
# source/blender/gpu/GPU_viewport.h
# source/blender/gpu/intern/gpu_shader_create_info_private.hh
# source/blender/gpu/intern/gpu_viewport.c
Diffstat (limited to 'source/blender/blenkernel/intern/vfont.c')
-rw-r--r-- | source/blender/blenkernel/intern/vfont.c | 1841 |
1 files changed, 1841 insertions, 0 deletions
diff --git a/source/blender/blenkernel/intern/vfont.c b/source/blender/blenkernel/intern/vfont.c new file mode 100644 index 00000000000..4a598288ee6 --- /dev/null +++ b/source/blender/blenkernel/intern/vfont.c @@ -0,0 +1,1841 @@ +/* + * 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. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup bke + */ + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <wctype.h> + +#include "CLG_log.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_ghash.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_math_base_safe.h" +#include "BLI_path_util.h" +#include "BLI_string.h" +#include "BLI_string_utf8.h" +#include "BLI_threads.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "DNA_curve_types.h" +#include "DNA_object_types.h" +#include "DNA_packedFile_types.h" +#include "DNA_vfont_types.h" + +#include "BKE_anim_path.h" +#include "BKE_bpath.h" +#include "BKE_curve.h" +#include "BKE_global.h" +#include "BKE_idtype.h" +#include "BKE_lib_id.h" +#include "BKE_main.h" +#include "BKE_packedFile.h" +#include "BKE_vfont.h" +#include "BKE_vfontdata.h" + +#include "BLO_read_write.h" + +static CLG_LogRef LOG = {"bke.data_transfer"}; +static ThreadRWMutex vfont_rwlock = BLI_RWLOCK_INITIALIZER; + +/**************************** Prototypes **************************/ + +static PackedFile *get_builtin_packedfile(void); + +/****************************** VFont Datablock ************************/ + +static void vfont_init_data(ID *id) +{ + VFont *vfont = (VFont *)id; + PackedFile *pf = get_builtin_packedfile(); + + if (pf) { + VFontData *vfd; + + vfd = BKE_vfontdata_from_freetypefont(pf); + if (vfd) { + vfont->data = vfd; + + BLI_strncpy(vfont->filepath, FO_BUILTIN_NAME, sizeof(vfont->filepath)); + } + + /* Free the packed file */ + BKE_packedfile_free(pf); + } +} + +static void vfont_copy_data(Main *UNUSED(bmain), + ID *id_dst, + const ID *UNUSED(id_src), + const int flag) +{ + VFont *vfont_dst = (VFont *)id_dst; + + /* We never handle usercount here for own data. */ + const int flag_subdata = flag | LIB_ID_CREATE_NO_USER_REFCOUNT; + + /* Just to be sure, should not have any value actually after reading time. */ + vfont_dst->temp_pf = NULL; + + if (vfont_dst->packedfile) { + vfont_dst->packedfile = BKE_packedfile_duplicate(vfont_dst->packedfile); + } + + if (vfont_dst->data) { + vfont_dst->data = BKE_vfontdata_copy(vfont_dst->data, flag_subdata); + } +} + +/** Free (or release) any data used by this font (does not free the font itself). */ +static void vfont_free_data(ID *id) +{ + VFont *vfont = (VFont *)id; + BKE_vfont_free_data(vfont); + + if (vfont->packedfile) { + BKE_packedfile_free(vfont->packedfile); + vfont->packedfile = NULL; + } +} + +static void vfont_foreach_path(ID *id, BPathForeachPathData *bpath_data) +{ + VFont *vfont = (VFont *)id; + + if (vfont->packedfile != NULL && (bpath_data->flag & BKE_BPATH_FOREACH_PATH_SKIP_PACKED) != 0) { + return; + } + + if (BKE_vfont_is_builtin(vfont)) { + return; + } + + BKE_bpath_foreach_path_fixed_process(bpath_data, vfont->filepath); +} + +static void vfont_blend_write(BlendWriter *writer, ID *id, const void *id_address) +{ + VFont *vf = (VFont *)id; + const bool is_undo = BLO_write_is_undo(writer); + + /* Clean up, important in undo case to reduce false detection of changed datablocks. */ + vf->data = NULL; + vf->temp_pf = NULL; + + /* Do not store packed files in case this is a library override ID. */ + if (ID_IS_OVERRIDE_LIBRARY(vf) && !is_undo) { + vf->packedfile = NULL; + } + + /* write LibData */ + BLO_write_id_struct(writer, VFont, id_address, &vf->id); + BKE_id_blend_write(writer, &vf->id); + + /* direct data */ + BKE_packedfile_blend_write(writer, vf->packedfile); +} + +static void vfont_blend_read_data(BlendDataReader *reader, ID *id) +{ + VFont *vf = (VFont *)id; + vf->data = NULL; + vf->temp_pf = NULL; + BKE_packedfile_blend_read(reader, &vf->packedfile); +} + +IDTypeInfo IDType_ID_VF = { + .id_code = ID_VF, + .id_filter = FILTER_ID_VF, + .main_listbase_index = INDEX_ID_VF, + .struct_size = sizeof(VFont), + .name = "Font", + .name_plural = "fonts", + .translation_context = BLT_I18NCONTEXT_ID_VFONT, + .flags = IDTYPE_FLAGS_NO_ANIMDATA | IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, + + .init_data = vfont_init_data, + .copy_data = vfont_copy_data, + .free_data = vfont_free_data, + .make_local = NULL, + .foreach_id = NULL, + .foreach_cache = NULL, + .foreach_path = vfont_foreach_path, + .owner_get = NULL, + + .blend_write = vfont_blend_write, + .blend_read_data = vfont_blend_read_data, + .blend_read_lib = NULL, + .blend_read_expand = NULL, + + .blend_read_undo_preserve = NULL, + + .lib_override_apply_post = NULL, +}; + +/***************************** VFont *******************************/ + +void BKE_vfont_free_data(struct VFont *vfont) +{ + if (vfont->data) { + if (vfont->data->characters) { + GHashIterator gh_iter; + GHASH_ITER (gh_iter, vfont->data->characters) { + VChar *che = BLI_ghashIterator_getValue(&gh_iter); + + while (che->nurbsbase.first) { + Nurb *nu = che->nurbsbase.first; + if (nu->bezt) { + MEM_freeN(nu->bezt); + } + BLI_freelinkN(&che->nurbsbase, nu); + } + + MEM_freeN(che); + } + + BLI_ghash_free(vfont->data->characters, NULL, NULL); + } + + MEM_freeN(vfont->data); + vfont->data = NULL; + } + + if (vfont->temp_pf) { + BKE_packedfile_free(vfont->temp_pf); /* NULL when the font file can't be found on disk */ + vfont->temp_pf = NULL; + } +} + +static const void *builtin_font_data = NULL; +static int builtin_font_size = 0; + +bool BKE_vfont_is_builtin(const struct VFont *vfont) +{ + return STREQ(vfont->filepath, FO_BUILTIN_NAME); +} + +void BKE_vfont_builtin_register(const void *mem, int size) +{ + builtin_font_data = mem; + builtin_font_size = size; +} + +static PackedFile *get_builtin_packedfile(void) +{ + if (!builtin_font_data) { + CLOG_ERROR(&LOG, "Internal error, builtin font not loaded"); + + return NULL; + } + + void *mem = MEM_mallocN(builtin_font_size, "vfd_builtin"); + + memcpy(mem, builtin_font_data, builtin_font_size); + + return BKE_packedfile_new_from_memory(mem, builtin_font_size); +} + +static VFontData *vfont_get_data(VFont *vfont) +{ + if (vfont == NULL) { + return NULL; + } + + /* And then set the data */ + if (!vfont->data) { + PackedFile *pf; + + BLI_rw_mutex_lock(&vfont_rwlock, THREAD_LOCK_WRITE); + + if (vfont->data) { + /* Check data again, since it might have been already + * initialized from other thread (previous check is + * not accurate or threading, just prevents unneeded + * lock if all the data is here for sure). + */ + BLI_rw_mutex_unlock(&vfont_rwlock); + return vfont->data; + } + + if (BKE_vfont_is_builtin(vfont)) { + pf = get_builtin_packedfile(); + } + else { + if (vfont->packedfile) { + pf = vfont->packedfile; + + /* We need to copy a tmp font to memory unless it is already there */ + if (vfont->temp_pf == NULL) { + vfont->temp_pf = BKE_packedfile_duplicate(pf); + } + } + else { + pf = BKE_packedfile_new(NULL, vfont->filepath, ID_BLEND_PATH_FROM_GLOBAL(&vfont->id)); + + if (vfont->temp_pf == NULL) { + vfont->temp_pf = BKE_packedfile_new( + NULL, vfont->filepath, ID_BLEND_PATH_FROM_GLOBAL(&vfont->id)); + } + } + if (!pf) { + CLOG_WARN(&LOG, "Font file doesn't exist: %s", vfont->filepath); + + /* DON'T DO THIS + * missing file shouldn't modify path! - campbell */ +#if 0 + strcpy(vfont->filepath, FO_BUILTIN_NAME); +#endif + pf = get_builtin_packedfile(); + } + } + + if (pf) { + vfont->data = BKE_vfontdata_from_freetypefont(pf); + if (pf != vfont->packedfile) { + BKE_packedfile_free(pf); + } + } + + BLI_rw_mutex_unlock(&vfont_rwlock); + } + + return vfont->data; +} + +VFont *BKE_vfont_load(Main *bmain, const char *filepath) +{ + char filename[FILE_MAXFILE]; + VFont *vfont = NULL; + PackedFile *pf; + bool is_builtin; + + if (STREQ(filepath, FO_BUILTIN_NAME)) { + BLI_strncpy(filename, filepath, sizeof(filename)); + + pf = get_builtin_packedfile(); + is_builtin = true; + } + else { + BLI_split_file_part(filepath, filename, sizeof(filename)); + pf = BKE_packedfile_new(NULL, filepath, BKE_main_blendfile_path(bmain)); + + is_builtin = false; + } + + if (pf) { + VFontData *vfd; + + vfd = BKE_vfontdata_from_freetypefont(pf); + if (vfd) { + /* If there's a font name, use it for the ID name. */ + vfont = BKE_libblock_alloc(bmain, ID_VF, vfd->name[0] ? vfd->name : filename, 0); + vfont->data = vfd; + BLI_strncpy(vfont->filepath, filepath, sizeof(vfont->filepath)); + + /* if auto-pack is on store the packed-file in de font structure */ + if (!is_builtin && (G.fileflags & G_FILE_AUTOPACK)) { + vfont->packedfile = pf; + } + + /* Do not add #FO_BUILTIN_NAME to temporary list-base. */ + if (!STREQ(filename, FO_BUILTIN_NAME)) { + vfont->temp_pf = BKE_packedfile_new(NULL, filepath, BKE_main_blendfile_path(bmain)); + } + } + + /* Free the packed file */ + if (!vfont || vfont->packedfile != pf) { + BKE_packedfile_free(pf); + } + } + + return vfont; +} + +VFont *BKE_vfont_load_exists_ex(struct Main *bmain, const char *filepath, bool *r_exists) +{ + VFont *vfont; + char str[FILE_MAX], strtest[FILE_MAX]; + + BLI_strncpy(str, filepath, sizeof(str)); + BLI_path_abs(str, BKE_main_blendfile_path(bmain)); + + /* first search an identical filepath */ + for (vfont = bmain->fonts.first; vfont; vfont = vfont->id.next) { + BLI_strncpy(strtest, vfont->filepath, sizeof(vfont->filepath)); + BLI_path_abs(strtest, ID_BLEND_PATH(bmain, &vfont->id)); + + if (BLI_path_cmp(strtest, str) == 0) { + id_us_plus(&vfont->id); /* officially should not, it doesn't link here! */ + if (r_exists) { + *r_exists = true; + } + return vfont; + } + } + + if (r_exists) { + *r_exists = false; + } + return BKE_vfont_load(bmain, filepath); +} + +VFont *BKE_vfont_load_exists(struct Main *bmain, const char *filepath) +{ + return BKE_vfont_load_exists_ex(bmain, filepath, NULL); +} + +static VFont *which_vfont(Curve *cu, CharInfo *info) +{ + switch (info->flag & (CU_CHINFO_BOLD | CU_CHINFO_ITALIC)) { + case CU_CHINFO_BOLD: + return cu->vfontb ? cu->vfontb : cu->vfont; + case CU_CHINFO_ITALIC: + return cu->vfonti ? cu->vfonti : cu->vfont; + case (CU_CHINFO_BOLD | CU_CHINFO_ITALIC): + return cu->vfontbi ? cu->vfontbi : cu->vfont; + default: + return cu->vfont; + } +} + +VFont *BKE_vfont_builtin_get(void) +{ + VFont *vfont; + + for (vfont = G_MAIN->fonts.first; vfont; vfont = vfont->id.next) { + if (BKE_vfont_is_builtin(vfont)) { + return vfont; + } + } + + return BKE_vfont_load(G_MAIN, FO_BUILTIN_NAME); +} + +static VChar *find_vfont_char(VFontData *vfd, unsigned int character) +{ + return BLI_ghash_lookup(vfd->characters, POINTER_FROM_UINT(character)); +} + +static void build_underline(Curve *cu, + ListBase *nubase, + const rctf *rect, + float yofs, + float rot, + int charidx, + short mat_nr, + const float font_size) +{ + Nurb *nu2; + BPoint *bp; + + nu2 = (Nurb *)MEM_callocN(sizeof(Nurb), "underline_nurb"); + nu2->resolu = cu->resolu; + nu2->bezt = NULL; + nu2->knotsu = nu2->knotsv = NULL; + nu2->charidx = charidx + 1000; + if (mat_nr > 0) { + nu2->mat_nr = mat_nr - 1; + } + nu2->pntsu = 4; + nu2->pntsv = 1; + nu2->orderu = 4; + nu2->orderv = 1; + nu2->flagu = CU_NURB_CYCLIC; + + bp = (BPoint *)MEM_calloc_arrayN(4, sizeof(BPoint), "underline_bp"); + + copy_v4_fl4(bp[0].vec, rect->xmin, (rect->ymax + yofs), 0.0f, 1.0f); + copy_v4_fl4(bp[1].vec, rect->xmax, (rect->ymax + yofs), 0.0f, 1.0f); + copy_v4_fl4(bp[2].vec, rect->xmax, (rect->ymin + yofs), 0.0f, 1.0f); + copy_v4_fl4(bp[3].vec, rect->xmin, (rect->ymin + yofs), 0.0f, 1.0f); + + /* Used by curve extrusion. */ + bp[0].radius = bp[1].radius = bp[2].radius = bp[3].radius = 1.0f; + + nu2->bp = bp; + BLI_addtail(nubase, nu2); + + if (rot != 0.0f) { + float si = sinf(rot); + float co = cosf(rot); + + for (int i = nu2->pntsu; i > 0; i--) { + float *fp = bp->vec; + + float x = fp[0] - rect->xmin; + float y = fp[1] - rect->ymin; + + fp[0] = (+co * x + si * y) + rect->xmin; + fp[1] = (-si * x + co * y) + rect->ymin; + + bp++; + } + + bp = nu2->bp; + } + + mul_v2_fl(bp[0].vec, font_size); + mul_v2_fl(bp[1].vec, font_size); + mul_v2_fl(bp[2].vec, font_size); + mul_v2_fl(bp[3].vec, font_size); +} + +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) { + return; + } + + /* make a copy at distance ofsx, ofsy with shear */ + float shear = cu->shear; + float si = sinf(rot); + float co = cosf(rot); + + VChar *che = find_vfont_char(vfd, character); + + /* Select the glyph data */ + Nurb *nu1 = NULL; + if (che) { + nu1 = che->nurbsbase.first; + } + + /* Create the character */ + while (nu1) { + BezTriple *bezt1 = nu1->bezt; + if (bezt1) { + Nurb *nu2 = (Nurb *)MEM_mallocN(sizeof(Nurb), "duplichar_nurb"); + if (nu2 == NULL) { + break; + } + memcpy(nu2, nu1, sizeof(struct Nurb)); + nu2->resolu = cu->resolu; + nu2->bp = NULL; + nu2->knotsu = nu2->knotsv = NULL; + nu2->flag = CU_SMOOTH; + nu2->charidx = charidx; + if (info->mat_nr > 0) { + nu2->mat_nr = info->mat_nr - 1; + } + else { + nu2->mat_nr = 0; + } + /* nu2->trim.first = 0; */ + /* nu2->trim.last = 0; */ + int u = nu2->pntsu; + + BezTriple *bezt2 = (BezTriple *)MEM_malloc_arrayN(u, sizeof(BezTriple), "duplichar_bezt2"); + if (bezt2 == NULL) { + MEM_freeN(nu2); + break; + } + memcpy(bezt2, bezt1, u * sizeof(struct BezTriple)); + nu2->bezt = bezt2; + + if (shear != 0.0f) { + bezt2 = nu2->bezt; + + for (int i = nu2->pntsu; i > 0; i--) { + bezt2->vec[0][0] += shear * bezt2->vec[0][1]; + bezt2->vec[1][0] += shear * bezt2->vec[1][1]; + bezt2->vec[2][0] += shear * bezt2->vec[2][1]; + bezt2++; + } + } + if (rot != 0.0f) { + bezt2 = nu2->bezt; + for (int i = nu2->pntsu; i > 0; i--) { + float *fp = bezt2->vec[0]; + + float x = fp[0]; + fp[0] = co * x + si * fp[1]; + fp[1] = -si * x + co * fp[1]; + x = fp[3]; + fp[3] = co * x + si * fp[4]; + fp[4] = -si * x + co * fp[4]; + x = fp[6]; + fp[6] = co * x + si * fp[7]; + fp[7] = -si * x + co * fp[7]; + + bezt2++; + } + } + bezt2 = nu2->bezt; + + if (info->flag & CU_CHINFO_SMALLCAPS_CHECK) { + const float sca = cu->smallcaps_scale; + for (int i = nu2->pntsu; i > 0; i--) { + float *fp = bezt2->vec[0]; + fp[0] *= sca; + fp[1] *= sca; + fp[3] *= sca; + fp[4] *= sca; + fp[6] *= sca; + fp[7] *= sca; + bezt2++; + } + } + bezt2 = nu2->bezt; + + for (int i = nu2->pntsu; i > 0; i--) { + float *fp = bezt2->vec[0]; + fp[0] = (fp[0] + ofsx) * fsize; + fp[1] = (fp[1] + ofsy) * fsize; + fp[3] = (fp[3] + ofsx) * fsize; + fp[4] = (fp[4] + ofsy) * fsize; + fp[6] = (fp[6] + ofsx) * fsize; + fp[7] = (fp[7] + ofsy) * fsize; + bezt2++; + } + + BLI_addtail(nubase, nu2); + } + + nu1 = nu1->next; + } +} + +int BKE_vfont_select_get(Object *ob, int *r_start, int *r_end) +{ + Curve *cu = ob->data; + EditFont *ef = cu->editfont; + int start, end, direction; + + if ((ob->type != OB_FONT) || (ef == NULL)) { + return 0; + } + + BLI_assert(ef->len >= 0); + BLI_assert(ef->selstart >= 0 && ef->selstart <= ef->len + 1); + BLI_assert(ef->selend >= 0 && ef->selend <= ef->len + 1); + BLI_assert(ef->pos >= 0 && ef->pos <= ef->len); + + if (ef->selstart == 0) { + return 0; + } + + if (ef->selstart <= ef->selend) { + start = ef->selstart - 1; + end = ef->selend - 1; + direction = 1; + } + else { + start = ef->selend; + end = ef->selstart - 2; + direction = -1; + } + + if (start == end + 1) { + return 0; + } + + BLI_assert(start < end + 1); + *r_start = start; + *r_end = end; + return direction; +} + +void BKE_vfont_select_clamp(Object *ob) +{ + Curve *cu = ob->data; + EditFont *ef = cu->editfont; + + BLI_assert((ob->type == OB_FONT) && ef); + + CLAMP_MAX(ef->pos, ef->len); + CLAMP_MAX(ef->selstart, ef->len + 1); + CLAMP_MAX(ef->selend, ef->len); +} + +static float char_width(Curve *cu, VChar *che, CharInfo *info) +{ + /* The character wasn't found, probably ascii = 0, then the width shall be 0 as well */ + if (che == NULL) { + return 0.0f; + } + if (info->flag & CU_CHINFO_SMALLCAPS_CHECK) { + return che->width * cu->smallcaps_scale; + } + + return che->width; +} + +static void textbox_scale(TextBox *tb_dst, const TextBox *tb_src, float scale) +{ + tb_dst->x = tb_src->x * scale; + tb_dst->y = tb_src->y * scale; + tb_dst->w = tb_src->w * scale; + tb_dst->h = tb_src->h * scale; +} + +/** + * Used for storing per-line data for alignment & wrapping. + */ +struct TempLineInfo { + float x_min; /* left margin */ + float x_max; /* right margin */ + int char_nr; /* number of characters */ + int wspace_nr; /* number of white-spaces of line */ +}; + +/* -------------------------------------------------------------------- */ +/** \name VFont Scale Overflow + * + * Scale the font to fit inside #TextBox bounds. + * + * - Scale horizontally when #TextBox.h is zero, + * otherwise scale vertically, allowing the text to wrap horizontally. + * - Never increase scale to fit, only ever scale on overflow. + * \{ */ + +typedef struct VFontToCurveIter { + int iteraction; + float scale_to_fit; + struct { + float min; + float max; + } bisect; + bool ok; + /** + * Wrap words that extends beyond the text-box width (enabled by default). + * + * Currently only disabled when scale-to-fit is enabled, + * so floating-point error doesn't cause unexpected wrapping, see T89241. + * + * \note This should only be set once, in the #VFONT_TO_CURVE_INIT pass + * otherwise iterations wont behave predictably, see T91401. + */ + bool word_wrap; + int status; +} VFontToCurveIter; + +enum { + VFONT_TO_CURVE_INIT = 0, + VFONT_TO_CURVE_BISECT, + VFONT_TO_CURVE_SCALE_ONCE, + VFONT_TO_CURVE_DONE, +}; + +#define FONT_TO_CURVE_SCALE_ITERATIONS 20 +#define FONT_TO_CURVE_SCALE_THRESHOLD 0.0001f + +/** \} */ + +/** + * Font metric values explained: + * + * Baseline: Line where the text "rests", used as the origin vertical position for the glyphs. + * Em height: Space most glyphs should fit within. + * Ascent: the recommended distance above the baseline to fit most characters. + * Descent: the recommended distance below the baseline to fit most characters. + * + * We obtain ascent and descent from the font itself (FT_Face->ascender / face->height). + * And in some cases it is even the same value as FT_Face->bbox.yMax/yMin + * (font top and bottom respectively). + * + * The em_height here is relative to FT_Face->bbox. + */ + +static float vfont_ascent(const VFontData *vfd) +{ + return vfd->ascender * vfd->em_height; +} +static float vfont_descent(const VFontData *vfd) +{ + return vfd->em_height - vfont_ascent(vfd); +} + +static bool vfont_to_curve(Object *ob, + Curve *cu, + int mode, + VFontToCurveIter *iter_data, + ListBase *r_nubase, + const char32_t **r_text, + int *r_text_len, + bool *r_text_free, + struct CharTrans **r_chartransdata) +{ + EditFont *ef = cu->editfont; + EditFontSelBox *selboxes = NULL; + VFont *vfont, *oldvfont; + VFontData *vfd = NULL; + CharInfo *info = NULL, *custrinfo; + TextBox tb_scale; + bool use_textbox; + VChar *che; + struct CharTrans *chartransdata = NULL, *ct; + struct TempLineInfo *lineinfo; + float *f, xof, yof, xtrax, linedist; + float twidth = 0, maxlen = 0; + int i, slen, j; + int curbox; + int selstart = 0, selend = 0; + int cnr = 0, lnr = 0, wsnr = 0; + const char32_t *mem = NULL; + char32_t ascii; + bool ok = false; + const float font_size = cu->fsize * iter_data->scale_to_fit; + const bool word_wrap = iter_data->word_wrap; + const float xof_scale = safe_divide(cu->xof, font_size); + const float yof_scale = safe_divide(cu->yof, font_size); + int last_line = -1; + /* Length of the text disregarding \n breaks. */ + float current_line_length = 0.0f; + float longest_line_length = 0.0f; + + /* Text at the beginning of the last used text-box (use for y-axis alignment). + * We over-allocate by one to simplify logic of getting last char. */ + int *i_textbox_array = MEM_callocN(sizeof(*i_textbox_array) * (cu->totbox + 1), + "TextBox initial char index"); + +#define MARGIN_X_MIN (xof_scale + tb_scale.x) +#define MARGIN_Y_MIN (yof_scale + tb_scale.y) + + /* remark: do calculations including the trailing '\0' of a string + * because the cursor can be at that location */ + + BLI_assert(ob == NULL || ob->type == OB_FONT); + + /* Set font data */ + vfont = cu->vfont; + + if (cu->str == NULL) { + return ok; + } + if (vfont == NULL) { + return ok; + } + + vfd = vfont_get_data(vfont); + + /* The VFont Data can not be found */ + if (!vfd) { + return ok; + } + + if (ef) { + slen = ef->len; + mem = ef->textbuf; + custrinfo = ef->textbufinfo; + } + else { + char32_t *mem_tmp; + slen = cu->len_char32; + + /* Create unicode string */ + mem_tmp = MEM_malloc_arrayN((slen + 1), sizeof(*mem_tmp), "convertedmem"); + if (!mem_tmp) { + return ok; + } + + BLI_str_utf8_as_utf32(mem_tmp, cu->str, slen + 1); + + if (cu->strinfo == NULL) { /* old file */ + cu->strinfo = MEM_calloc_arrayN((slen + 4), sizeof(CharInfo), "strinfo compat"); + } + custrinfo = cu->strinfo; + if (!custrinfo) { + return ok; + } + + mem = mem_tmp; + } + + if (cu->tb == NULL) { + cu->tb = MEM_calloc_arrayN(MAXTEXTBOX, sizeof(TextBox), "TextBox compat"); + } + + if (ef != NULL && ob != NULL) { + if (ef->selboxes) { + MEM_freeN(ef->selboxes); + } + + if (BKE_vfont_select_get(ob, &selstart, &selend)) { + ef->selboxes_len = (selend - selstart) + 1; + ef->selboxes = MEM_calloc_arrayN(ef->selboxes_len, sizeof(EditFontSelBox), "font selboxes"); + } + else { + ef->selboxes_len = 0; + ef->selboxes = NULL; + } + + selboxes = ef->selboxes; + } + + /* calc offset and rotation of each char */ + ct = chartransdata = MEM_calloc_arrayN((slen + 1), sizeof(struct CharTrans), "buildtext"); + + /* We assume the worst case: 1 character per line (is freed at end anyway) */ + lineinfo = MEM_malloc_arrayN((slen * 2 + 1), sizeof(*lineinfo), "lineinfo"); + + linedist = cu->linedist; + + curbox = 0; + textbox_scale(&tb_scale, &cu->tb[curbox], safe_divide(1.0f, font_size)); + use_textbox = (tb_scale.w != 0.0f); + + xof = MARGIN_X_MIN; + yof = MARGIN_Y_MIN; + + xtrax = 0.5f * cu->spacing - 0.5f; + + oldvfont = NULL; + + for (i = 0; i < slen; i++) { + custrinfo[i].flag &= ~(CU_CHINFO_WRAP | CU_CHINFO_SMALLCAPS_CHECK | CU_CHINFO_OVERFLOW); + } + + for (i = 0; i <= slen; i++) { + makebreak: + /* Characters in the list */ + info = &custrinfo[i]; + ascii = mem[i]; + if (info->flag & CU_CHINFO_SMALLCAPS) { + ascii = towupper(ascii); + if (mem[i] != ascii) { + info->flag |= CU_CHINFO_SMALLCAPS_CHECK; + } + } + + vfont = which_vfont(cu, info); + + if (vfont == NULL) { + break; + } + + if (vfont != oldvfont) { + vfd = vfont_get_data(vfont); + oldvfont = vfont; + } + + /* VFont Data for VFont couldn't be found */ + if (!vfd) { + MEM_freeN(chartransdata); + chartransdata = NULL; + MEM_freeN(lineinfo); + goto finally; + } + + if (!ELEM(ascii, '\n', '\0')) { + BLI_rw_mutex_lock(&vfont_rwlock, THREAD_LOCK_READ); + che = find_vfont_char(vfd, ascii); + BLI_rw_mutex_unlock(&vfont_rwlock); + + /* + * The character wasn't in the current curve base so load it + * But if the font is built-in then do not try loading since + * whole font is in the memory already + */ + if (che == NULL && BKE_vfont_is_builtin(vfont) == false) { + BLI_rw_mutex_lock(&vfont_rwlock, THREAD_LOCK_WRITE); + /* Check it once again, char might have been already load + * between previous BLI_rw_mutex_unlock() and this BLI_rw_mutex_lock(). + * + * Such a check should not be a bottleneck since it wouldn't + * happen often once all the chars are load. + */ + if ((che = find_vfont_char(vfd, ascii)) == NULL) { + che = BKE_vfontdata_char_from_freetypefont(vfont, ascii); + } + BLI_rw_mutex_unlock(&vfont_rwlock); + } + } + else { + che = NULL; + } + + twidth = char_width(cu, che, info); + + /* Calculate positions. */ + + if ((tb_scale.w != 0.0f) && (ct->dobreak == 0)) { /* May need wrapping. */ + const float x_available = xof_scale + tb_scale.w; + const float x_used = (xof - tb_scale.x) + twidth; + + if (word_wrap == false) { + /* When scale to fit is used, don't do any wrapping. + * + * Floating precision error can cause the text to be slightly larger. + * Assert this is a small value as large values indicate incorrect + * calculations with scale-to-fit which shouldn't be ignored. See T89241. */ + if (x_used > x_available) { + BLI_assert_msg(compare_ff_relative(x_used, x_available, FLT_EPSILON, 64), + "VFontToCurveIter.scale_to_fit not set correctly!"); + } + } + else if (x_used > x_available) { + // CLOG_WARN(&LOG, "linewidth exceeded: %c%c%c...", mem[i], mem[i+1], mem[i+2]); + for (j = i; j && (mem[j] != '\n') && (chartransdata[j].dobreak == 0); j--) { + bool dobreak = false; + if (ELEM(mem[j], ' ', '-')) { + ct -= (i - (j - 1)); + cnr -= (i - (j - 1)); + if (mem[j] == ' ') { + wsnr--; + } + if (mem[j] == '-') { + wsnr++; + } + i = j - 1; + xof = ct->xof; + ct[1].dobreak = 1; + custrinfo[i + 1].flag |= CU_CHINFO_WRAP; + dobreak = true; + } + else if (chartransdata[j].dobreak) { + // CLOG_WARN(&LOG, "word too long: %c%c%c...", mem[j], mem[j+1], mem[j+2]); + ct->dobreak = 1; + custrinfo[i + 1].flag |= CU_CHINFO_WRAP; + ct -= 1; + cnr -= 1; + i--; + xof = ct->xof; + dobreak = true; + } + if (dobreak) { + if (tb_scale.h == 0.0f) { + /* NOTE: If underlined text is truncated away, the extra space is also truncated. */ + custrinfo[i + 1].flag |= CU_CHINFO_OVERFLOW; + } + goto makebreak; + } + } + } + } + + if (ascii == '\n' || ascii == 0 || ct->dobreak) { + ct->xof = xof; + ct->yof = yof; + ct->linenr = lnr; + ct->charnr = cnr; + + yof -= linedist; + + lineinfo[lnr].x_min = (xof - xtrax) - tb_scale.x; + lineinfo[lnr].x_max = tb_scale.w; + lineinfo[lnr].char_nr = cnr; + lineinfo[lnr].wspace_nr = wsnr; + + CLAMP_MIN(maxlen, lineinfo[lnr].x_min); + + if ((tb_scale.h != 0.0f) && ((-(yof - tb_scale.y)) > (tb_scale.h - linedist) - yof_scale)) { + if (cu->totbox > (curbox + 1)) { + maxlen = 0; + curbox++; + i_textbox_array[curbox] = i + 1; + + textbox_scale(&tb_scale, &cu->tb[curbox], 1.0f / font_size); + + yof = MARGIN_Y_MIN; + } + else if (last_line == -1) { + last_line = lnr + 1; + info->flag |= CU_CHINFO_OVERFLOW; + } + } + + current_line_length += xof - MARGIN_X_MIN; + if (ct->dobreak) { + current_line_length += twidth; + } + else { + longest_line_length = MAX2(current_line_length, longest_line_length); + current_line_length = 0.0f; + } + + /* XXX(campbell): has been unused for years, need to check if this is useful, r4613 r5282. */ +#if 0 + if (ascii == '\n') { + xof = xof_scale; + } + else { + xof = MARGIN_X_MIN; + } +#else + xof = MARGIN_X_MIN; +#endif + lnr++; + cnr = 0; + wsnr = 0; + } + else if (ascii == 9) { /* TAB */ + float tabfac; + + ct->xof = xof; + ct->yof = yof; + ct->linenr = lnr; + ct->charnr = cnr++; + + tabfac = (xof - MARGIN_X_MIN + 0.01f); + tabfac = 2.0f * ceilf(tabfac / 2.0f); + xof = MARGIN_X_MIN + tabfac; + } + else { + EditFontSelBox *sb = NULL; + float wsfac; + + ct->xof = xof; + ct->yof = yof; + ct->linenr = lnr; + ct->charnr = cnr++; + + if (selboxes && (i >= selstart) && (i <= selend)) { + sb = &selboxes[i - selstart]; + sb->y = yof * font_size - linedist * font_size * 0.1f; + sb->h = linedist * font_size; + sb->w = xof * font_size; + } + + if (ascii == 32) { + wsfac = cu->wordspace; + wsnr++; + } + else { + wsfac = 1.0f; + } + + /* Set the width of the character */ + twidth = char_width(cu, che, info); + + xof += (twidth * wsfac * (1.0f + (info->kern / 40.0f))) + xtrax; + + if (sb) { + sb->w = (xof * font_size) - sb->w; + } + } + ct++; + } + current_line_length += xof + twidth - MARGIN_X_MIN; + longest_line_length = MAX2(current_line_length, longest_line_length); + + cu->lines = 1; + for (i = 0; i <= slen; i++) { + ascii = mem[i]; + ct = &chartransdata[i]; + if (ascii == '\n' || ct->dobreak) { + cu->lines++; + } + } + + /* Line-data is now: width of line. */ + + if (cu->spacemode != CU_ALIGN_X_LEFT) { + ct = chartransdata; + + if (cu->spacemode == CU_ALIGN_X_RIGHT) { + struct TempLineInfo *li; + + for (i = 0, li = lineinfo; i < lnr; i++, li++) { + li->x_min = (li->x_max - li->x_min) + xof_scale; + } + + for (i = 0; i <= slen; i++) { + ct->xof += lineinfo[ct->linenr].x_min; + ct++; + } + } + else if (cu->spacemode == CU_ALIGN_X_MIDDLE) { + struct TempLineInfo *li; + + for (i = 0, li = lineinfo; i < lnr; i++, li++) { + li->x_min = ((li->x_max - li->x_min) + xof_scale) / 2.0f; + } + + for (i = 0; i <= slen; i++) { + ct->xof += lineinfo[ct->linenr].x_min; + ct++; + } + } + else if ((cu->spacemode == CU_ALIGN_X_FLUSH) && use_textbox) { + struct TempLineInfo *li; + + for (i = 0, li = lineinfo; i < lnr; i++, li++) { + li->x_min = ((li->x_max - li->x_min) + xof_scale); + + if (li->char_nr > 1) { + li->x_min /= (float)(li->char_nr - 1); + } + } + for (i = 0; i <= slen; i++) { + for (j = i; (!ELEM(mem[j], '\0', '\n')) && (chartransdata[j].dobreak == 0) && (j < slen); + j++) { + /* do nothing */ + } + + // if ((mem[j] != '\n') && (mem[j])) { + ct->xof += ct->charnr * lineinfo[ct->linenr].x_min; + // } + ct++; + } + } + else if ((cu->spacemode == CU_ALIGN_X_JUSTIFY) && use_textbox) { + float curofs = 0.0f; + for (i = 0; i <= slen; i++) { + for (j = i; (mem[j]) && (mem[j] != '\n') && (chartransdata[j].dobreak == 0) && (j < slen); + j++) { + /* pass */ + } + + if ((mem[j] != '\n') && ((chartransdata[j].dobreak != 0))) { + if (mem[i] == ' ') { + struct TempLineInfo *li; + + li = &lineinfo[ct->linenr]; + curofs += ((li->x_max - li->x_min) + xof_scale) / (float)li->wspace_nr; + } + ct->xof += curofs; + } + if (mem[i] == '\n' || chartransdata[i].dobreak) { + curofs = 0; + } + ct++; + } + } + } + + /* top-baseline is default, in this case, do nothing */ + if (cu->align_y != CU_ALIGN_Y_TOP_BASELINE) { + if (tb_scale.h != 0.0f) { + /* We need to loop all the text-boxes even the "full" ones. + * This way they all get the same vertical padding. */ + for (int tb_index = 0; tb_index < cu->totbox; tb_index++) { + struct CharTrans *ct_first, *ct_last; + const int i_textbox = i_textbox_array[tb_index]; + const int i_textbox_next = i_textbox_array[tb_index + 1]; + const bool is_last_filled_textbox = ELEM(i_textbox_next, 0, slen + 1); + int lines; + + ct_first = chartransdata + i_textbox; + ct_last = chartransdata + (is_last_filled_textbox ? slen : i_textbox_next - 1); + lines = ct_last->linenr - ct_first->linenr + 1; + + if (cu->overflow == CU_OVERFLOW_TRUNCATE) { + /* Ensure overflow doesn't truncate text, before centering vertically + * giving odd/buggy results, see: T66614. */ + if ((tb_index == cu->totbox - 1) && (last_line != -1)) { + lines = last_line - ct_first->linenr; + } + } + + textbox_scale(&tb_scale, &cu->tb[tb_index], 1.0f / font_size); + /* The initial Y origin of the textbox is hardcoded to 1.0f * text scale. */ + const float textbox_y_origin = 1.0f; + float yoff = 0.0f; + + switch (cu->align_y) { + case CU_ALIGN_Y_TOP_BASELINE: + break; + case CU_ALIGN_Y_TOP: + yoff = textbox_y_origin - vfont_ascent(vfd); + break; + case CU_ALIGN_Y_CENTER: + yoff = ((((vfd->em_height + (lines - 1) * linedist) * 0.5f) - vfont_ascent(vfd)) - + (tb_scale.h * 0.5f) + textbox_y_origin); + break; + case CU_ALIGN_Y_BOTTOM_BASELINE: + yoff = textbox_y_origin + ((lines - 1) * linedist) - tb_scale.h; + break; + case CU_ALIGN_Y_BOTTOM: + yoff = textbox_y_origin + ((lines - 1) * linedist) - tb_scale.h + vfont_descent(vfd); + break; + } + + for (ct = ct_first; ct <= ct_last; ct++) { + ct->yof += yoff; + } + + if (is_last_filled_textbox) { + break; + } + } + } + else { + /* Non text-box case handled separately. */ + float yoff = 0.0f; + + switch (cu->align_y) { + case CU_ALIGN_Y_TOP_BASELINE: + break; + case CU_ALIGN_Y_TOP: + yoff = -vfont_ascent(vfd); + break; + case CU_ALIGN_Y_CENTER: + yoff = ((vfd->em_height + (lnr - 1) * linedist) * 0.5f) - vfont_ascent(vfd); + break; + case CU_ALIGN_Y_BOTTOM_BASELINE: + yoff = (lnr - 1) * linedist; + break; + case CU_ALIGN_Y_BOTTOM: + yoff = (lnr - 1) * linedist + vfont_descent(vfd); + break; + } + + ct = chartransdata; + for (i = 0; i <= slen; i++) { + ct->yof += yoff; + ct++; + } + } + } + + MEM_freeN(lineinfo); + MEM_freeN(i_textbox_array); + + /* TEXT ON CURVE */ + /* NOTE: Only OB_CURVE objects could have a path. */ + if (cu->textoncurve && cu->textoncurve->type == OB_CURVE) { + BLI_assert(cu->textoncurve->runtime.curve_cache != NULL); + if (cu->textoncurve->runtime.curve_cache != NULL && + cu->textoncurve->runtime.curve_cache->anim_path_accum_length != NULL) { + float distfac, imat[4][4], imat3[3][3], cmat[3][3]; + float minx, maxx; + float timeofs, sizefac; + + if (ob != NULL) { + invert_m4_m4(imat, ob->obmat); + } + else { + unit_m4(imat); + } + copy_m3_m4(imat3, imat); + + copy_m3_m4(cmat, cu->textoncurve->obmat); + mul_m3_m3m3(cmat, cmat, imat3); + sizefac = normalize_v3(cmat[0]) / font_size; + + ct = chartransdata; + minx = maxx = ct->xof; + ct++; + for (i = 1; i <= slen; i++, ct++) { + if (minx > ct->xof) { + minx = ct->xof; + } + if (maxx < ct->xof) { + maxx = ct->xof; + } + } + + /* We put the x-coordinate exact at the curve, the y is rotated. */ + + /* length correction */ + const float chartrans_size_x = maxx - minx; + if (chartrans_size_x != 0.0f) { + const CurveCache *cc = cu->textoncurve->runtime.curve_cache; + const float totdist = BKE_anim_path_get_length(cc); + distfac = (sizefac * totdist) / chartrans_size_x; + distfac = (distfac > 1.0f) ? (1.0f / distfac) : 1.0f; + } + else { + /* Happens when there are no characters, set this value to place the text cursor. */ + distfac = 0.0f; + } + + timeofs = 0.0f; + + if (distfac < 1.0f) { + /* Path longer than text: space-mode is involved. */ + + if (cu->spacemode == CU_ALIGN_X_RIGHT) { + timeofs = 1.0f - distfac; + } + else if (cu->spacemode == CU_ALIGN_X_MIDDLE) { + timeofs = (1.0f - distfac) / 2.0f; + } + else if (cu->spacemode == CU_ALIGN_X_FLUSH) { + distfac = 1.0f; + } + } + + if (chartrans_size_x != 0.0f) { + distfac /= chartrans_size_x; + } + + timeofs += distfac * cu->xof; /* not cyclic */ + + ct = chartransdata; + for (i = 0; i <= slen; i++, ct++) { + float ctime, dtime, vec[4], tvec[4], rotvec[3]; + float si, co; + + /* Rotate around center character. */ + info = &custrinfo[i]; + ascii = mem[i]; + if (info->flag & CU_CHINFO_SMALLCAPS_CHECK) { + ascii = towupper(ascii); + } + + che = find_vfont_char(vfd, ascii); + + twidth = char_width(cu, che, info); + + dtime = distfac * 0.5f * twidth; + + ctime = timeofs + distfac * (ct->xof - minx); + CLAMP(ctime, 0.0f, 1.0f); + + /* calc the right loc AND the right rot separately */ + /* vec, tvec need 4 items */ + BKE_where_on_path(cu->textoncurve, ctime, vec, tvec, NULL, NULL, NULL); + BKE_where_on_path(cu->textoncurve, ctime + dtime, tvec, rotvec, NULL, NULL, NULL); + + mul_v3_fl(vec, sizefac); + + ct->rot = (float)M_PI - atan2f(rotvec[1], rotvec[0]); + + si = sinf(ct->rot); + co = cosf(ct->rot); + + yof = ct->yof; + + ct->xof = vec[0] + si * yof; + ct->yof = vec[1] + co * yof; + + if (selboxes && (i >= selstart) && (i <= selend)) { + EditFontSelBox *sb; + sb = &selboxes[i - selstart]; + sb->rot = -ct->rot; + } + } + } + } + + if (selboxes) { + ct = chartransdata; + for (i = 0; i <= selend; i++, ct++) { + if (i >= selstart) { + selboxes[i - selstart].x = ct->xof * font_size; + selboxes[i - selstart].y = ct->yof * font_size; + } + } + } + + if (ELEM(mode, FO_CURSUP, FO_CURSDOWN, FO_PAGEUP, FO_PAGEDOWN) && + iter_data->status == VFONT_TO_CURVE_INIT) { + ct = &chartransdata[ef->pos]; + + if (ELEM(mode, FO_CURSUP, FO_PAGEUP) && ct->linenr == 0) { + /* pass */ + } + else if (ELEM(mode, FO_CURSDOWN, FO_PAGEDOWN) && ct->linenr == lnr) { + /* pass */ + } + else { + switch (mode) { + case FO_CURSUP: + lnr = ct->linenr - 1; + break; + case FO_CURSDOWN: + lnr = ct->linenr + 1; + break; + case FO_PAGEUP: + lnr = ct->linenr - 10; + break; + case FO_PAGEDOWN: + lnr = ct->linenr + 10; + break; + } + cnr = ct->charnr; + /* seek for char with lnr en cnr */ + ef->pos = 0; + ct = chartransdata; + for (i = 0; i < slen; i++) { + if (ct->linenr == lnr) { + if ((ct->charnr == cnr) || ((ct + 1)->charnr == 0)) { + break; + } + } + else if (ct->linenr > lnr) { + break; + } + ef->pos++; + ct++; + } + } + } + + /* cursor first */ + if (ef) { + float si, co; + + ct = &chartransdata[ef->pos]; + si = sinf(ct->rot); + co = cosf(ct->rot); + + f = ef->textcurs[0]; + + f[0] = font_size * (-0.1f * co + ct->xof); + f[1] = font_size * (0.1f * si + ct->yof); + + f[2] = font_size * (0.1f * co + ct->xof); + f[3] = font_size * (-0.1f * si + ct->yof); + + f[4] = font_size * (0.1f * co + 0.8f * si + ct->xof); + f[5] = font_size * (-0.1f * si + 0.8f * co + ct->yof); + + f[6] = font_size * (-0.1f * co + 0.8f * si + ct->xof); + f[7] = font_size * (0.1f * si + 0.8f * co + ct->yof); + } + + if (mode == FO_SELCHANGE) { + MEM_freeN(chartransdata); + chartransdata = NULL; + } + else if (mode == FO_EDIT) { + /* Make NURBS-data. */ + BKE_nurbList_free(r_nubase); + + ct = chartransdata; + for (i = 0; i < slen; i++) { + unsigned int cha = (unsigned int)mem[i]; + info = &(custrinfo[i]); + + if ((cu->overflow == CU_OVERFLOW_TRUNCATE) && (ob && ob->mode != OB_MODE_EDIT) && + (info->flag & CU_CHINFO_OVERFLOW)) { + break; + } + + if (info->flag & CU_CHINFO_SMALLCAPS_CHECK) { + cha = towupper(cha); + } + + /* Only do that check in case we do have an object, otherwise all materials get erased every + * time that code is called without an object... */ + if (ob != NULL && (info->mat_nr > (ob->totcol))) { + // CLOG_ERROR( + // &LOG, "Illegal material index (%d) in text object, setting to 0", info->mat_nr); + info->mat_nr = 0; + } + /* We do not want to see any character for \n or \r */ + if (cha != '\n') { + 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')) { + float ulwidth, uloverlap = 0.0f; + rctf rect; + + if ((i < (slen - 1)) && (mem[i + 1] != '\n') && + ((mem[i + 1] != ' ') || (custrinfo[i + 1].flag & CU_CHINFO_UNDERLINE)) && + ((custrinfo[i + 1].flag & CU_CHINFO_WRAP) == 0)) { + uloverlap = xtrax + 0.1f; + } + /* Find the character, the characters has to be in the memory already + * since character checking has been done earlier already. */ + che = find_vfont_char(vfd, cha); + + twidth = char_width(cu, che, info); + ulwidth = (twidth * (1.0f + (info->kern / 40.0f))) + uloverlap; + + rect.xmin = ct->xof; + rect.xmax = rect.xmin + ulwidth; + + rect.ymin = ct->yof; + rect.ymax = rect.ymin - cu->ulheight; + + build_underline( + cu, r_nubase, &rect, cu->ulpos - 0.05f, ct->rot, i, info->mat_nr, font_size); + } + ct++; + } + } + + if (iter_data->status == VFONT_TO_CURVE_SCALE_ONCE) { + /* That means we were in a final run, just exit. */ + BLI_assert(cu->overflow == CU_OVERFLOW_SCALE); + iter_data->status = VFONT_TO_CURVE_DONE; + } + else if (cu->overflow == CU_OVERFLOW_NONE) { + /* Do nothing. */ + } + else if ((tb_scale.h == 0.0f) && (tb_scale.w == 0.0f)) { + /* Do nothing. */ + } + else if (cu->overflow == CU_OVERFLOW_SCALE) { + if ((cu->totbox == 1) && ((tb_scale.w == 0.0f) || (tb_scale.h == 0.0f))) { + /* These are special cases, simpler to deal with. */ + if (tb_scale.w == 0.0f) { + /* This is a potential vertical overflow. + * Since there is no width limit, all the new lines are from line breaks. */ + if ((last_line != -1) && (lnr > last_line)) { + const float total_text_height = lnr * linedist; + iter_data->scale_to_fit = tb_scale.h / total_text_height; + iter_data->status = VFONT_TO_CURVE_SCALE_ONCE; + iter_data->word_wrap = false; + } + } + else if (tb_scale.h == 0.0f) { + /* This is a horizontal overflow. */ + if (longest_line_length > tb_scale.w) { + /* We make sure longest line before it broke can fit here. */ + float scale_to_fit = tb_scale.w / longest_line_length; + + iter_data->scale_to_fit = scale_to_fit; + iter_data->status = VFONT_TO_CURVE_SCALE_ONCE; + iter_data->word_wrap = false; + } + } + } + else { + /* This is the really complicated case, the best we can do is to iterate over + * this function a few times until we get an acceptable result. + * + * Keep in mind that there is no single number that will make all fit to the end. + * In a way, our ultimate goal is to get the highest scale that still leads to the + * number of extra lines to zero. + */ + if (iter_data->status == VFONT_TO_CURVE_INIT) { + bool valid = true; + + for (int tb_index = 0; tb_index <= curbox; tb_index++) { + TextBox *tb = &cu->tb[tb_index]; + if ((tb->w == 0.0f) || (tb->h == 0.0f)) { + valid = false; + break; + } + } + + if (valid && (last_line != -1) && (lnr > last_line)) { + const float total_text_height = lnr * linedist; + float scale_to_fit = tb_scale.h / total_text_height; + + iter_data->bisect.max = 1.0f; + iter_data->bisect.min = scale_to_fit; + + iter_data->status = VFONT_TO_CURVE_BISECT; + } + } + else { + BLI_assert(iter_data->status == VFONT_TO_CURVE_BISECT); + /* Try to get the highest scale that gives us the exactly + * number of lines we need. */ + bool valid = false; + + if ((last_line != -1) && (lnr > last_line)) { + /* It is overflowing, scale it down. */ + iter_data->bisect.max = iter_data->scale_to_fit; + } + else { + /* It fits inside the textbox, scale it up. */ + iter_data->bisect.min = iter_data->scale_to_fit; + valid = true; + } + + /* Bisecting to try to find the best fit. */ + iter_data->scale_to_fit = (iter_data->bisect.max + iter_data->bisect.min) * 0.5f; + + /* We iterated enough or got a good enough result. */ + if ((!iter_data->iteraction--) || ((iter_data->bisect.max - iter_data->bisect.min) < + (cu->fsize * FONT_TO_CURVE_SCALE_THRESHOLD))) { + if (valid) { + iter_data->status = VFONT_TO_CURVE_DONE; + } + else { + iter_data->scale_to_fit = iter_data->bisect.min; + iter_data->status = VFONT_TO_CURVE_SCALE_ONCE; + } + } + } + } + } + + /* Scale to fit only works for single text box layouts. */ + if (ELEM(iter_data->status, VFONT_TO_CURVE_SCALE_ONCE, VFONT_TO_CURVE_BISECT)) { + /* Always cleanup before going to the scale-to-fit repetition. */ + if (r_nubase != NULL) { + BKE_nurbList_free(r_nubase); + } + + if (chartransdata != NULL) { + MEM_freeN(chartransdata); + } + + if (ef == NULL) { + MEM_freeN((void *)mem); + } + return true; + } + + ok = true; +finally: + if (r_text) { + *r_text = mem; + *r_text_len = slen; + *r_text_free = (ef == NULL); + } + else { + if (ef == NULL) { + MEM_freeN((void *)mem); + } + } + + if (chartransdata) { + if (ok && r_chartransdata) { + *r_chartransdata = chartransdata; + } + else { + MEM_freeN(chartransdata); + } + } + + /* Store the effective scale, to use for the textbox lines. */ + cu->fsize_realtime = font_size; + + return ok; + +#undef MARGIN_X_MIN +#undef MARGIN_Y_MIN +} + +#undef DESCENT +#undef ASCENT + +bool BKE_vfont_to_curve_ex(Object *ob, + Curve *cu, + int mode, + ListBase *r_nubase, + const char32_t **r_text, + int *r_text_len, + bool *r_text_free, + struct CharTrans **r_chartransdata) +{ + VFontToCurveIter data = { + .iteraction = cu->totbox * FONT_TO_CURVE_SCALE_ITERATIONS, + .scale_to_fit = 1.0f, + .word_wrap = true, + .ok = true, + .status = VFONT_TO_CURVE_INIT, + }; + + do { + data.ok &= vfont_to_curve( + ob, cu, mode, &data, r_nubase, r_text, r_text_len, r_text_free, r_chartransdata); + } while (data.ok && ELEM(data.status, VFONT_TO_CURVE_SCALE_ONCE, VFONT_TO_CURVE_BISECT)); + + return data.ok; +} + +#undef FONT_TO_CURVE_SCALE_ITERATIONS +#undef FONT_TO_CURVE_SCALE_THRESHOLD + +bool BKE_vfont_to_curve_nubase(Object *ob, int mode, ListBase *r_nubase) +{ + BLI_assert(ob->type == OB_FONT); + + return BKE_vfont_to_curve_ex(ob, ob->data, mode, r_nubase, NULL, NULL, NULL, NULL); +} + +bool BKE_vfont_to_curve(Object *ob, int mode) +{ + Curve *cu = ob->data; + + return BKE_vfont_to_curve_ex(ob, ob->data, mode, &cu->nurb, NULL, NULL, NULL, NULL); +} + +/* -------------------------------------------------------------------- */ +/** \name VFont Clipboard + * \{ */ + +static struct { + char32_t *text_buffer; + CharInfo *info_buffer; + size_t len_utf32; + size_t len_utf8; +} g_vfont_clipboard = {NULL}; + +void BKE_vfont_clipboard_free(void) +{ + MEM_SAFE_FREE(g_vfont_clipboard.text_buffer); + MEM_SAFE_FREE(g_vfont_clipboard.info_buffer); + g_vfont_clipboard.len_utf32 = 0; + g_vfont_clipboard.len_utf8 = 0; +} + +void BKE_vfont_clipboard_set(const char32_t *text_buf, const CharInfo *info_buf, const size_t len) +{ + char32_t *text; + CharInfo *info; + + /* Clean previous buffers. */ + BKE_vfont_clipboard_free(); + + text = MEM_malloc_arrayN((len + 1), sizeof(*text), __func__); + if (text == NULL) { + return; + } + + info = MEM_malloc_arrayN(len, sizeof(CharInfo), __func__); + if (info == NULL) { + MEM_freeN(text); + return; + } + + memcpy(text, text_buf, len * sizeof(*text)); + text[len] = '\0'; + memcpy(info, info_buf, len * sizeof(CharInfo)); + + /* store new buffers */ + g_vfont_clipboard.text_buffer = text; + g_vfont_clipboard.info_buffer = info; + g_vfont_clipboard.len_utf8 = BLI_str_utf32_as_utf8_len(text); + g_vfont_clipboard.len_utf32 = len; +} + +void BKE_vfont_clipboard_get(char32_t **r_text_buf, + CharInfo **r_info_buf, + size_t *r_len_utf8, + size_t *r_len_utf32) +{ + if (r_text_buf) { + *r_text_buf = g_vfont_clipboard.text_buffer; + } + + if (r_info_buf) { + *r_info_buf = g_vfont_clipboard.info_buffer; + } + + if (r_len_utf32) { + *r_len_utf32 = g_vfont_clipboard.len_utf32; + } + + if (r_len_utf8) { + *r_len_utf8 = g_vfont_clipboard.len_utf8; + } +} + +/** \} */ |