/* SPDX-FileCopyrightText: 2009 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup edinterface */ #include #include #include #include #include "MEM_guardedalloc.h" #include "DNA_userdef_types.h" #include "BLI_listbase.h" #include "BLI_rect.h" #include "BLI_string.h" #include "BLI_utildefines.h" #include "BKE_global.hh" #include "BLF_api.hh" #include "UI_interface.hh" #include "interface_intern.hh" #ifdef WIN32 # include "BLI_math_base.h" /* M_PI */ #endif using blender::StringRef; static void fontstyle_set_ex(const uiFontStyle *fs, const float dpi_fac); /* style + theme + layout-engine = UI */ /** * This is a complete set of layout rules, the 'state' of the Layout * Engine. Multiple styles are possible, defined via C or Python. Styles * get a name, and will typically get activated per region type, like * `Header`, or `Listview` or `Toolbar`. Properties of Style definitions * are: * * - default column properties, internal spacing, aligning, min/max width * - button alignment rules (for groups) * - label placement rules * - internal labeling or external labeling default * - default minimum widths for buttons/labels (in amount of characters) * - font types, styles and relative sizes for Panel titles, labels, etc. */ /* ********************************************** */ static uiStyle *ui_style_new(ListBase *styles, const char *name, short uifont_id) { uiStyle *style = MEM_callocN(__func__); BLI_addtail(styles, style); STRNCPY(style->name, name); style->panelzoom = 1.0; /* unused */ style->paneltitle.uifont_id = uifont_id; style->paneltitle.points = UI_DEFAULT_TITLE_POINTS; style->paneltitle.character_weight = 400; style->paneltitle.shadow = 3; style->paneltitle.shadx = 0; style->paneltitle.shady = -1; style->paneltitle.shadowalpha = 0.5f; style->paneltitle.shadowcolor = 0.0f; style->grouplabel.uifont_id = uifont_id; style->grouplabel.points = UI_DEFAULT_TITLE_POINTS; style->grouplabel.character_weight = 400; style->grouplabel.shadow = 3; style->grouplabel.shadx = 0; style->grouplabel.shady = -1; style->grouplabel.shadowalpha = 0.5f; style->grouplabel.shadowcolor = 0.0f; style->widget.uifont_id = uifont_id; style->widget.points = UI_DEFAULT_TEXT_POINTS; style->widget.character_weight = 400; style->widget.shadow = 1; style->widget.shady = -1; style->widget.shadowalpha = 0.5f; style->widget.shadowcolor = 0.0f; style->tooltip.uifont_id = uifont_id; style->tooltip.points = UI_DEFAULT_TOOLTIP_POINTS; style->tooltip.character_weight = 400; style->tooltip.shadow = 1; style->tooltip.shady = -1; style->tooltip.shadowalpha = 0.5f; style->tooltip.shadowcolor = 0.0f; style->columnspace = 8; style->templatespace = 5; style->boxspace = 5; style->buttonspacex = 8; style->buttonspacey = 2; style->panelspace = 8; style->panelouter = 4; return style; } static uiFont *uifont_to_blfont(int id) { uiFont *font = static_cast(U.uifonts.first); for (; font; font = font->next) { if (font->uifont_id == id) { return font; } } return static_cast(U.uifonts.first); } /* *************** draw ************************ */ void UI_fontstyle_draw_ex(const uiFontStyle *fs, const rcti *rect, const char *str, const size_t str_len, const uchar col[4], const uiFontStyleDraw_Params *fs_params, int *r_xofs, int *r_yofs, ResultBLF *r_info) { int xofs = 0, yofs; int font_flag = BLF_CLIPPING; UI_fontstyle_set(fs); /* set the flag */ if (fs->shadow) { font_flag |= BLF_SHADOW; const float shadow_color[4] = { fs->shadowcolor, fs->shadowcolor, fs->shadowcolor, fs->shadowalpha}; BLF_shadow(fs->uifont_id, FontShadowType(fs->shadow), shadow_color); BLF_shadow_offset(fs->uifont_id, fs->shadx, fs->shady); } if (fs_params->word_wrap == 1) { font_flag |= BLF_WORD_WRAP; } if (fs->bold) { font_flag |= BLF_BOLD; } if (fs->italic) { font_flag |= BLF_ITALIC; } BLF_enable(fs->uifont_id, font_flag); if (fs_params->word_wrap == 1) { /* Draw from bound-box top. */ yofs = BLI_rcti_size_y(rect) - BLF_height_max(fs->uifont_id); } else { /* Draw from bound-box center. */ const int height = BLF_ascender(fs->uifont_id) + BLF_descender(fs->uifont_id); yofs = ceil(0.5f * (BLI_rcti_size_y(rect) - height)); } if (fs_params->align == UI_STYLE_TEXT_CENTER) { xofs = floor(0.5f * (BLI_rcti_size_x(rect) - BLF_width(fs->uifont_id, str, str_len))); } else if (fs_params->align == UI_STYLE_TEXT_RIGHT) { xofs = BLI_rcti_size_x(rect) - BLF_width(fs->uifont_id, str, str_len); } yofs = std::max(0, yofs); xofs = std::max(0, xofs); BLF_clipping(fs->uifont_id, rect->xmin, rect->ymin, rect->xmax, rect->ymax); BLF_position(fs->uifont_id, rect->xmin + xofs, rect->ymin + yofs, 0.0f); BLF_color4ubv(fs->uifont_id, col); BLF_draw(fs->uifont_id, str, str_len, r_info); BLF_disable(fs->uifont_id, font_flag); if (r_xofs) { *r_xofs = xofs; } if (r_yofs) { *r_yofs = yofs; } } void UI_fontstyle_draw(const uiFontStyle *fs, const rcti *rect, const char *str, const size_t str_len, const uchar col[4], const uiFontStyleDraw_Params *fs_params) { UI_fontstyle_draw_ex(fs, rect, str, str_len, col, fs_params, nullptr, nullptr, nullptr); } void UI_fontstyle_draw_multiline_clipped_ex(const uiFontStyle *fs, const rcti *rect, const char *str, const uchar col[4], const eFontStyle_Align align, int *r_xofs, int *r_yofs, ResultBLF *r_info) { int xofs = 0, yofs; int font_flag = BLF_CLIPPING; /* Recommended for testing: Results should be the same with or without BLF clipping since the * string is wrapped and shortened to fit. Disabling it can help spot issues. */ // font_flag &= ~BLF_CLIPPING; UI_fontstyle_set(fs); /* set the flag */ if (fs->shadow) { font_flag |= BLF_SHADOW; const float shadow_color[4] = { fs->shadowcolor, fs->shadowcolor, fs->shadowcolor, fs->shadowalpha}; BLF_shadow(fs->uifont_id, FontShadowType(fs->shadow), shadow_color); BLF_shadow_offset(fs->uifont_id, fs->shadx, fs->shady); } if (fs->bold) { font_flag |= BLF_BOLD; } if (fs->italic) { font_flag |= BLF_ITALIC; } BLF_enable(fs->uifont_id, font_flag); const int max_width = BLI_rcti_size_x(rect); const int max_height = BLI_rcti_size_y(rect); const int line_height = BLF_height_max(fs->uifont_id); const int max_line_count = max_height / line_height; BLF_clipping(fs->uifont_id, rect->xmin, rect->ymin, rect->xmax, rect->ymax); BLF_color4ubv(fs->uifont_id, col); char str_buf[UI_MAX_DRAW_STR]; const blender::Vector lines = UI_text_clip_multiline_middle( fs, str, str_buf, sizeof(str_buf), max_width, max_line_count); BLI_assert(lines.size() <= max_line_count); /* Draw so that overall text is centered vertically. */ yofs = (max_height + lines.size() * line_height) / 2.0f - BLF_ascender(fs->uifont_id) - /* Not sure subtracting the descender is always wanted, * gives best results where this is currently used. */ BLF_descender(fs->uifont_id) / 2.0f; yofs = std::max(0, yofs); ResultBLF line_result = {0, 0}; /* Draw each line with the given alignment. */ for (StringRef line : lines) { /* String wrapping might have trailing/leading white-space. */ line = line.trim(); if (align == UI_STYLE_TEXT_CENTER) { xofs = floor(0.5f * (max_width - BLF_width(fs->uifont_id, line.data(), line.size()))); } else if (align == UI_STYLE_TEXT_RIGHT) { xofs = max_width - BLF_width(fs->uifont_id, line.data(), line.size()); } xofs = std::max(0, xofs); BLF_position(fs->uifont_id, rect->xmin + xofs, rect->ymin + yofs, 0.0f); BLF_draw(fs->uifont_id, line.data(), line.size(), &line_result); yofs -= line_height; } if (r_info) { r_info->width = rect->xmin + xofs + line_result.width; r_info->lines = lines.size(); } BLF_disable(fs->uifont_id, font_flag); if (r_xofs) { *r_xofs = xofs; } if (r_yofs) { *r_yofs = yofs; } } void UI_fontstyle_draw_multiline_clipped(const uiFontStyle *fs, const rcti *rect, const char *str, const uchar col[4], const eFontStyle_Align align) { UI_fontstyle_draw_multiline_clipped_ex(fs, rect, str, col, align, nullptr, nullptr, nullptr); } void UI_fontstyle_draw_rotated(const uiFontStyle *fs, const rcti *rect, const char *str, const uchar col[4]) { float height; int xofs, yofs; float angle; rcti txtrect; UI_fontstyle_set(fs); height = BLF_ascender(fs->uifont_id) + BLF_descender(fs->uifont_id); /* becomes x-offset when rotated */ xofs = ceil(0.5f * (BLI_rcti_size_y(rect) - height)); /* ignore UI_STYLE, always aligned to top */ /* Rotate counter-clockwise for now (assumes left-to-right language). */ xofs += height; yofs = BLF_width(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX) + 5; angle = M_PI_2; /* translate rect to vertical */ txtrect.xmin = rect->xmin - BLI_rcti_size_y(rect); txtrect.ymin = rect->ymin - BLI_rcti_size_x(rect); txtrect.xmax = rect->xmin; txtrect.ymax = rect->ymin; /* clip is very strict, so we give it some space */ /* clipping is done without rotation, so make rect big enough to contain both positions */ BLF_clipping(fs->uifont_id, txtrect.xmin - 1, txtrect.ymin - yofs - xofs - 4, rect->xmax + 1, rect->ymax + 4); BLF_enable(fs->uifont_id, BLF_CLIPPING); BLF_position(fs->uifont_id, txtrect.xmin + xofs, txtrect.ymax - yofs, 0.0f); BLF_enable(fs->uifont_id, BLF_ROTATION); BLF_rotation(fs->uifont_id, angle); BLF_color4ubv(fs->uifont_id, col); if (fs->shadow) { BLF_enable(fs->uifont_id, BLF_SHADOW); const float shadow_color[4] = { fs->shadowcolor, fs->shadowcolor, fs->shadowcolor, fs->shadowalpha}; BLF_shadow(fs->uifont_id, FontShadowType(fs->shadow), shadow_color); BLF_shadow_offset(fs->uifont_id, fs->shadx, fs->shady); } BLF_draw(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); BLF_disable(fs->uifont_id, BLF_ROTATION); BLF_disable(fs->uifont_id, BLF_CLIPPING); if (fs->shadow) { BLF_disable(fs->uifont_id, BLF_SHADOW); } } void UI_fontstyle_draw_simple( const uiFontStyle *fs, float x, float y, const char *str, const uchar col[4]) { UI_fontstyle_set(fs); BLF_position(fs->uifont_id, x, y, 0.0f); BLF_color4ubv(fs->uifont_id, col); BLF_draw(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); } void UI_fontstyle_draw_simple_backdrop(const uiFontStyle *fs, float x, float y, const blender::StringRef str, const float col_fg[4], const float col_bg[4]) { UI_fontstyle_set(fs); { const int width = BLF_width(fs->uifont_id, str.data(), str.size()); const int height = BLF_height_max(fs->uifont_id); const int decent = BLF_descender(fs->uifont_id); const float margin = height / 4.0f; rctf rect; rect.xmin = x - margin; rect.xmax = x + width + margin; rect.ymin = (y + decent) - margin; rect.ymax = (y + decent) + height + margin; UI_draw_roundbox_corner_set(UI_CNR_ALL); UI_draw_roundbox_4fv(&rect, true, margin, col_bg); } BLF_position(fs->uifont_id, x, y, 0.0f); BLF_color4fv(fs->uifont_id, col_fg); BLF_draw(fs->uifont_id, str.data(), str.size()); } /* ************** helpers ************************ */ const uiStyle *UI_style_get() { #if 0 uiStyle *style = nullptr; /* offset is two struct uiStyle pointers */ style = BLI_findstring(&U.uistyles, "Unifont Style", sizeof(style) * 2); return (style != nullptr) ? style : U.uistyles.first; #else return static_cast(U.uistyles.first); #endif } const uiStyle *UI_style_get_dpi() { const uiStyle *style = UI_style_get(); static uiStyle _style; _style = *style; _style.paneltitle.shadx = short(UI_SCALE_FAC * _style.paneltitle.shadx); _style.paneltitle.shady = short(UI_SCALE_FAC * _style.paneltitle.shady); _style.grouplabel.shadx = short(UI_SCALE_FAC * _style.grouplabel.shadx); _style.grouplabel.shady = short(UI_SCALE_FAC * _style.grouplabel.shady); _style.widget.shadx = short(UI_SCALE_FAC * _style.widget.shadx); _style.widget.shady = short(UI_SCALE_FAC * _style.widget.shady); _style.tooltip.shadx = short(UI_SCALE_FAC * _style.tooltip.shadx); _style.tooltip.shady = short(UI_SCALE_FAC * _style.tooltip.shady); _style.columnspace = short(UI_SCALE_FAC * _style.columnspace); _style.templatespace = short(UI_SCALE_FAC * _style.templatespace); _style.boxspace = short(UI_SCALE_FAC * _style.boxspace); _style.buttonspacex = short(UI_SCALE_FAC * _style.buttonspacex); _style.buttonspacey = short(UI_SCALE_FAC * _style.buttonspacey); _style.panelspace = short(UI_SCALE_FAC * _style.panelspace); _style.panelouter = short(UI_SCALE_FAC * _style.panelouter); return &_style; } int UI_fontstyle_string_width(const uiFontStyle *fs, const char *str) { UI_fontstyle_set(fs); return int(BLF_width(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX)); } int UI_fontstyle_string_width_with_block_aspect(const uiFontStyle *fs, const StringRef str, const float aspect) { /* FIXME(@ideasman42): the final scale of the font is rounded which should be accounted for. * Failing to do so causes bad alignment when zoomed out very far in the node-editor. */ fontstyle_set_ex(fs, UI_SCALE_FAC / aspect); return int(BLF_width(fs->uifont_id, str.data(), str.size()) * aspect); } int UI_fontstyle_height_max(const uiFontStyle *fs) { UI_fontstyle_set(fs); return BLF_height_max(fs->uifont_id); } /* ************** init exit ************************ */ void uiStyleInit() { const uiStyle *style = static_cast(U.uistyles.first); /* Recover from uninitialized DPI. */ if (U.dpi == 0) { U.dpi = 72; } CLAMP(U.dpi, 48, 144); /* Needed so that custom fonts are always first. */ BLF_unload_all(); uiFont *font_first = static_cast(U.uifonts.first); /* default builtin */ if (font_first == nullptr) { font_first = MEM_callocN(__func__); BLI_addtail(&U.uifonts, font_first); } if (U.font_path_ui[0]) { STRNCPY(font_first->filepath, U.font_path_ui); font_first->uifont_id = UIFONT_CUSTOM1; } else { STRNCPY(font_first->filepath, "default"); font_first->uifont_id = UIFONT_DEFAULT; } LISTBASE_FOREACH (uiFont *, font, &U.uifonts) { const bool unique = false; if (font->uifont_id == UIFONT_DEFAULT) { font->blf_id = BLF_load_default(unique); } else { font->blf_id = BLF_load(font->filepath); if (font->blf_id == -1) { font->blf_id = BLF_load_default(unique); } } BLF_default_set(font->blf_id); if (font->blf_id == -1) { if (G.debug & G_DEBUG) { printf("%s: error, no fonts available\n", __func__); } } } if (style == nullptr) { style = ui_style_new(&U.uistyles, "Default Style", UIFONT_DEFAULT); } BLF_cache_flush_set_fn(UI_widgetbase_draw_cache_flush); BLF_default_size(style->widget.points); /* XXX, this should be moved into a style, * but for now best only load the monospaced font once. */ BLI_assert(blf_mono_font == -1); /* Use unique font loading to avoid thread safety issues with mono font * used for render metadata stamp in threads. */ if (U.font_path_ui_mono[0]) { blf_mono_font = BLF_load_unique(U.font_path_ui_mono); } if (blf_mono_font == -1) { const bool unique = true; blf_mono_font = BLF_load_mono_default(unique); } /* Set default flags based on UI preferences (not render fonts) */ { const int flag_disable = (BLF_MONOCHROME | BLF_HINTING_NONE | BLF_HINTING_SLIGHT | BLF_HINTING_FULL | BLF_RENDER_SUBPIXELAA); int flag_enable = 0; if (U.text_render & USER_TEXT_HINTING_NONE) { flag_enable |= BLF_HINTING_NONE; } else if (U.text_render & USER_TEXT_HINTING_SLIGHT) { flag_enable |= BLF_HINTING_SLIGHT; } else if (U.text_render & USER_TEXT_HINTING_FULL) { flag_enable |= BLF_HINTING_FULL; } if (U.text_render & USER_TEXT_DISABLE_AA) { flag_enable |= BLF_MONOCHROME; } else { if (U.text_render & USER_TEXT_RENDER_SUBPIXELAA) { flag_enable |= BLF_RENDER_SUBPIXELAA; } } LISTBASE_FOREACH (uiFont *, font, &U.uifonts) { if (font->blf_id != -1) { BLF_disable(font->blf_id, flag_disable); BLF_enable(font->blf_id, flag_enable); } } if (blf_mono_font != -1) { BLF_disable(blf_mono_font, flag_disable); BLF_enable(blf_mono_font, flag_enable); } } /** * Second for rendering else we get threading problems, * * \note This isn't good that the render font depends on the preferences, * keep for now though, since without this there is no way to display many unicode chars. */ if (blf_mono_font_render == -1) { const bool unique = true; blf_mono_font_render = BLF_load_mono_default(unique); } /* Load the fallback fonts last. */ BLF_load_font_stack(); } static void fontstyle_set_ex(const uiFontStyle *fs, const float dpi_fac) { const uiFont *font = uifont_to_blfont(fs->uifont_id); BLF_size(font->blf_id, fs->points * dpi_fac); BLF_character_weight(font->blf_id, fs->character_weight); } void UI_fontstyle_set(const uiFontStyle *fs) { fontstyle_set_ex(fs, UI_SCALE_FAC); }