Motivation is to disambiguate on the naming level what the matrix actually means. It is very easy to understand the meaning backwards, especially since in Python the name goes the opposite way (it is called `world_matrix` in the Python API). It is important to disambiguate the naming without making developers to look into the comment in the header file (which is also not super clear either). Additionally, more clear naming facilitates the unit verification (or, in this case, space validation) when reading an expression. This patch calls the matrix `object_to_world` which makes it clear from the local code what is it exactly going on. This is only done on DNA level, and a lot of local variables still follow the old naming. A DNA rename is setup in a way that there is no change on the file level, so there should be no regressions at all. The possibility is to add `_matrix` or `_mat` suffix to the name to make it explicit that it is a matrix. Although, not sure if it really helps the readability, or is it something redundant. Differential Revision: https://developer.blender.org/D16328
1824 lines
50 KiB
C
1824 lines
50 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later
|
|
* Copyright 2001-2002 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 user-count 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_pointer_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, uint 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,
|
|
uint 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)
|
|
|
|
/* NOTE: 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);
|
|
}
|
|
|
|
i = 0;
|
|
while (i <= slen) {
|
|
/* 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]);
|
|
bool dobreak = false;
|
|
for (j = i; (mem[j] != '\n') && (chartransdata[j].dobreak == 0); j--) {
|
|
|
|
/* Special case when there are no breaks possible. */
|
|
if (UNLIKELY(j == 0)) {
|
|
if (i == slen) {
|
|
/* Use the behavior of zero a height text-box when a break cannot be inserted.
|
|
*
|
|
* Typically when a text-box has any height and overflow is set to scale
|
|
* the text will wrap to fit the width as necessary. When wrapping isn't
|
|
* possible it's important to use the same code-path as zero-height lines.
|
|
* Without this exception a single word will not scale-to-fit (see: T95116). */
|
|
tb_scale.h = 0.0f;
|
|
}
|
|
break;
|
|
}
|
|
|
|
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;
|
|
break;
|
|
}
|
|
BLI_assert(chartransdata[j].dobreak == 0);
|
|
}
|
|
|
|
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;
|
|
}
|
|
/* Since a break was added, re-run this loop with `i` at it's new value. */
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
xof = MARGIN_X_MIN;
|
|
lnr++;
|
|
cnr = 0;
|
|
wsnr = 0;
|
|
}
|
|
else if (ascii == '\t') { /* Tab character. */
|
|
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 == ' ') { /* Space character. */
|
|
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++;
|
|
i++;
|
|
}
|
|
|
|
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 text-box is hard-coded 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_CURVES_LEGACY objects could have a path. */
|
|
if (cu->textoncurve && cu->textoncurve->type == OB_CURVES_LEGACY) {
|
|
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->object_to_world);
|
|
}
|
|
else {
|
|
unit_m4(imat);
|
|
}
|
|
copy_m3_m4(imat3, imat);
|
|
|
|
copy_m3_m4(cmat, cu->textoncurve->object_to_world);
|
|
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], 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);
|
|
|
|
/* Calculate the right loc AND the right rot separately. */
|
|
/* `vec` needs 4 items. */
|
|
BKE_where_on_path(cu->textoncurve, ctime, vec, NULL, NULL, NULL, NULL);
|
|
BKE_where_on_path(cu->textoncurve, ctime + dtime, NULL, 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 - 0.25f) * font_size;
|
|
selboxes[i - selstart].h = 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` & `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.02f * co + ct->xof);
|
|
f[1] = font_size * (0.1f * si - (0.25f * co) + ct->yof);
|
|
|
|
f[2] = font_size * (0.02f * co + ct->xof);
|
|
f[3] = font_size * (-0.1f * si - (0.25f * co) + ct->yof);
|
|
|
|
f[4] = font_size * (0.02f * co + 0.8f * si + ct->xof);
|
|
f[5] = font_size * (-0.1f * si + 0.75f * co + ct->yof);
|
|
|
|
f[6] = font_size * (-0.02f * co + 0.8f * si + ct->xof);
|
|
f[7] = font_size * (0.1f * si + 0.75f * 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++) {
|
|
uint cha = (uint)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 don't want to see any character for '\n'. */
|
|
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 text-box, 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 text-box 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;
|
|
}
|
|
}
|
|
|
|
/** \} */
|