Merge branch 'backport-font-feature-pango-hb' into 'gtk-3-24'

Backport font features code utilizing Pango with HarfBuzz integration (1.44.x) for gtk-3-24

See merge request GNOME/gtk!4206
This commit is contained in:
Matthias Clasen 2021-12-01 15:47:30 +00:00
commit 15d9b3c7d6
14 changed files with 569 additions and 185 deletions

View File

@ -1395,17 +1395,35 @@ fi
##################################################
# Check for harfbuzz and pangoft2
##################################################
PKG_CHECK_MODULES(GTK_FONT_CHOOSER_WIDGET,
harfbuzz >= 0.9 pangoft2,
harfbuzz >= 2.2.0 pango >= 1.44.0,
build_font_demo=yes,
build_font_demo=no)
PKG_CHECK_MODULES(GTK_FONT_CHOOSER_WIDGET_FT,
harfbuzz >= 0.9 pangoft2,
build_font_demo_ft=yes,
build_font_demo_ft=no)
if test "x$build_font_demo" = xno; then
if test "x$build_font_demo_ft" = xyes; then
build_font_demo=yes
else
build_font_demo=no
fi
fi
AM_CONDITIONAL(BUILD_FONT_DEMO, [ test "x$build_font_demo" = xyes ])
if test "x$build_font_demo" = xyes; then
AC_DEFINE([HAVE_HARFBUZZ], 1, [defines whether we have HarfBuzz])
AC_DEFINE([HAVE_PANGOFT], 1, [defines whether we have pangoft2])
GTK_DEP_CFLAGS="$GTK_DEP_CFLAGS $GTK_FONT_CHOOSER_WIDGET_CFLAGS"
GTK_DEP_LIBS="$GTK_DEP_LIBS $GTK_FONT_CHOOSER_WIDGET_LIBS"
if test "x$build_font_demo_ft" = xyes; then
AC_DEFINE([HAVE_PANGOFT], 1, [defines whether we have pangoft2])
GTK_DEP_CFLAGS="$GTK_DEP_CFLAGS $GTK_FONT_CHOOSER_WIDGET_FT_CFLAGS"
GTK_DEP_LIBS="$GTK_DEP_LIBS $GTK_FONT_CHOOSER_WIDGET_FT_LIBS"
else
GTK_DEP_CFLAGS="$GTK_DEP_CFLAGS $GTK_FONT_CHOOSER_WIDGET_CFLAGS"
GTK_DEP_LIBS="$GTK_DEP_LIBS $GTK_FONT_CHOOSER_WIDGET_LIBS"
fi
fi
if $PKG_CONFIG --exists x11; then

View File

@ -1,87 +1,15 @@
## Makefile.am for gtk+/demos
include $(top_srcdir)/Makefile.decl
## These should be in the order you want them to appear in the
## demo app, which means alphabetized by demo title, not filename
demos_base = \
application_demo.c \
assistant.c \
builder.c \
button_box.c \
changedisplay.c \
clipboard.c \
colorsel.c \
combobox.c \
css_accordion.c \
css_basics.c \
css_blendmodes.c \
css_multiplebgs.c \
css_pixbufs.c \
css_shadows.c \
cursors.c \
dialog.c \
drawingarea.c \
editable_cells.c \
entry_buffer.c \
entry_completion.c \
event_axes.c \
expander.c \
filtermodel.c \
fishbowl.c \
foreigndrawing.c \
gestures.c \
glarea.c \
headerbar.c \
hypertext.c \
iconview.c \
iconview_edit.c \
images.c \
infobar.c \
links.c \
listbox.c \
flowbox.c \
list_store.c \
markup.c \
menus.c \
modelbutton.c \
offscreen_window.c \
offscreen_window2.c \
overlay.c \
overlay2.c \
paint.c \
panes.c \
pickers.c \
pixbufs.c \
popover.c \
printing.c \
revealer.c \
rotated_text.c \
scale.c \
search_entry.c \
search_entry2.c \
shortcuts.c \
sidebar.c \
sizegroup.c \
spinbutton.c \
spinner.c \
stack.c \
tabs.c \
textmask.c \
textview.c \
textscroll.c \
theming_style_classes.c \
toolpalette.c \
transparent.c \
tree_store.c
include $(srcdir)/demos-sources.mak
demos_opt =
if BUILD_FONT_DEMO
demos_opt += font_features.c
demos_opt += $(font_features_demo)
endif
if OS_UNIX
demos_opt += pagesetup.c
demos_opt += $(page_setup_demo)
endif
demos = $(demos_base) $(demos_opt)

View File

@ -0,0 +1,75 @@
## These should be in the order you want them to appear in the
## demo app, which means alphabetized by demo title, not filename
demos_base = \
application_demo.c \
assistant.c \
builder.c \
button_box.c \
changedisplay.c \
clipboard.c \
colorsel.c \
combobox.c \
css_accordion.c \
css_basics.c \
css_blendmodes.c \
css_multiplebgs.c \
css_pixbufs.c \
css_shadows.c \
cursors.c \
dialog.c \
drawingarea.c \
editable_cells.c \
entry_buffer.c \
entry_completion.c \
event_axes.c \
expander.c \
filtermodel.c \
fishbowl.c \
foreigndrawing.c \
gestures.c \
glarea.c \
headerbar.c \
hypertext.c \
iconview.c \
iconview_edit.c \
images.c \
infobar.c \
links.c \
listbox.c \
flowbox.c \
list_store.c \
markup.c \
menus.c \
modelbutton.c \
offscreen_window.c \
offscreen_window2.c \
overlay.c \
overlay2.c \
paint.c \
panes.c \
pickers.c \
pixbufs.c \
popover.c \
printing.c \
revealer.c \
rotated_text.c \
scale.c \
search_entry.c \
search_entry2.c \
shortcuts.c \
sidebar.c \
sizegroup.c \
spinbutton.c \
spinner.c \
stack.c \
tabs.c \
textmask.c \
textview.c \
textscroll.c \
theming_style_classes.c \
toolpalette.c \
transparent.c \
tree_store.c
font_features_demo = font_features.c
page_setup_demo = pagesetup.c

View File

@ -9,10 +9,19 @@
*/
#include <gtk/gtk.h>
#if !(PANGO_VERSION_CHECK(1,44,0) && HB_VERSION_ATLEAST(2,2,0))
#define FONT_FEATURES_USE_PANGOFT2 1
#endif
#ifdef FONT_FEATURES_USE_PANGOFT2
#include <pango/pangofc-font.h>
#include <hb.h>
#include <hb-ot.h>
#include <hb-ft.h>
#else
#include <hb-ot.h>
#endif
static GtkWidget *label;
static GtkWidget *settings;
@ -205,19 +214,32 @@ static void
update_script_combo (void)
{
GtkListStore *store;
hb_font_t *hb_font;
hb_font_t *hb_font = NULL;
gint i, j, k, l;
FT_Face ft_face;
PangoFont *pango_font;
GHashTable *tags;
GHashTableIter iter;
TagPair *pair;
gboolean cleanup_hb_face = FALSE;
#ifdef FONT_FEATURES_USE_PANGOFT2
FT_Face ft_face;
#endif
store = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);
pango_font = get_pango_font ();
ft_face = pango_fc_font_lock_face (PANGO_FC_FONT (pango_font)),
hb_font = hb_ft_font_create (ft_face, NULL);
#ifdef FONT_FEATURES_USE_PANGOFT2
if (PANGO_IS_FC_FONT (pango_font)
{
ft_face = pango_fc_font_lock_face (PANGO_FC_FONT (pango_font)),
hb_font = hb_ft_font_create (ft_face, NULL);
cleanup_hb_face = TRUE;
}
#else
hb_font = pango_font_get_hb_font (pango_font);
#endif
tags = g_hash_table_new_full (tag_pair_hash, tag_pair_equal, g_free, NULL);
@ -264,10 +286,15 @@ update_script_combo (void)
}
}
hb_face_destroy (hb_face);
if (cleanup_hb_face)
hb_face_destroy (hb_face);
}
pango_fc_font_unlock_face (PANGO_FC_FONT (pango_font));
#ifdef FONT_FEATURES_USE_PANGOFT2
if (PANGO_IS_FC_FONT (pango_font)
pango_fc_font_unlock_face (PANGO_FC_FONT (pango_font));
#endif
g_object_unref (pango_font);
g_hash_table_iter_init (&iter, tags);
@ -346,8 +373,12 @@ update_features (void)
GtkTreeIter iter;
guint script_index, lang_index;
PangoFont *pango_font;
hb_font_t *hb_font = NULL;
gboolean cleanup_hb_face = FALSE;
#ifdef FONT_FEATURES_USE_PANGOFT2
FT_Face ft_face;
hb_font_t *hb_font;
#endif
for (i = 0; i < num_features; i++)
gtk_widget_set_opacity (icon[i], 0);
@ -364,8 +395,17 @@ update_features (void)
-1);
pango_font = get_pango_font ();
ft_face = pango_fc_font_lock_face (PANGO_FC_FONT (pango_font)),
hb_font = hb_ft_font_create (ft_face, NULL);
#ifdef FONT_FEATURES_USE_PANGOFT2
if (PANGO_IS_FC_FONT (pango_font)
{
ft_face = pango_fc_font_lock_face (PANGO_FC_FONT (pango_font)),
hb_font = hb_ft_font_create (ft_face, NULL);
cleanup_hb_face = TRUE;
}
#else
hb_font = pango_font_get_hb_font (pango_font);
#endif
if (hb_font)
{
@ -397,10 +437,15 @@ update_features (void)
}
}
hb_face_destroy (hb_face);
if (cleanup_hb_face)
hb_face_destroy (hb_face);
}
pango_fc_font_unlock_face (PANGO_FC_FONT (pango_font));
#ifdef FONT_FEATURES_USE_PANGOFT2
if (PANGO_IS_FC_FONT (pango_font)
pango_fc_font_unlock_face (PANGO_FC_FONT (pango_font));
#endif
g_object_unref (pango_font);
}

View File

@ -76,7 +76,13 @@ demos = files([
gtkdemo_deps = [libgtk_dep]
if harfbuzz_dep.found() and pangoft_dep.found()
if pango_dep.version().version_compare('>=1.44.0') and harfbuzz_dep.found() and cc.has_header_symbol(
'hb-ot.h',
'hb_ot_var_get_axis_count',
dependencies: harfbuzz_dep
)
demos += files('font_features.c')
elif harfbuzz_dep.found() and pangoft_dep.found()
demos += files('font_features.c')
gtkdemo_deps += [harfbuzz_dep, pangoft_dep]
endif

View File

@ -53,7 +53,15 @@
#include "gtkcombobox.h"
#include "gtkgesturemultipress.h"
#if defined(HAVE_HARFBUZZ) && defined(HAVE_PANGOFT)
#if (PANGO_VERSION_CHECK(1,44,0) && HB_VERSION_ATLEAST(2,2,0)) || \
(defined(HAVE_HARFBUZZ) && defined(HAVE_PANGOFT))
#define HAVE_FONT_FEATURES 1
#if !(PANGO_VERSION_CHECK(1,44,0) && HB_VERSION_ATLEAST(2,2,0))
#define FONT_FEATURES_USE_PANGOFT2 1
#endif
#endif
#ifdef FONT_FEATURES_USE_PANGOFT2
#include <pango/pangofc-font.h>
#include <hb.h>
#include <hb-ot.h>
@ -62,6 +70,12 @@
#include <freetype/ftmm.h>
#include "language-names.h"
#include "script-names.h"
#elif defined (HAVE_FONT_FEATURES)
#include <hb-ot.h>
#if defined (_MSC_VER) && defined (__MSVC_PROJECTS__)
#pragma comment(lib, "harfbuzz")
#endif
#endif
#include "open-type-layout.h"
@ -763,10 +777,11 @@ change_tweak (GSimpleAction *action,
g_simple_action_set_state (action, state);
}
#if defined(HAVE_HARFBUZZ) && defined(HAVE_PANGOFT)
#ifdef HAVE_FONT_FEATURES
typedef struct {
guint32 tag;
float default_value;
GtkAdjustment *adjustment;
GtkWidget *label;
GtkWidget *scale;
@ -823,7 +838,7 @@ gtk_font_chooser_widget_init (GtkFontChooserWidget *fontchooser)
gtk_widget_init_template (GTK_WIDGET (fontchooser));
#if defined(HAVE_HARFBUZZ) && defined(HAVE_PANGOFT)
#ifdef HAVE_FONT_FEATURES
priv->axes = g_hash_table_new_full (axis_hash, axis_equal, NULL, axis_free);
#endif
@ -864,7 +879,7 @@ gtk_font_chooser_widget_init (GtkFontChooserWidget *fontchooser)
/* Load data and set initial style-dependent parameters */
gtk_font_chooser_widget_load_fonts (fontchooser, TRUE);
#if defined(HAVE_HARFBUZZ) && defined(HAVE_PANGOFT)
#ifdef HAVE_FONT_FEATURES
gtk_font_chooser_widget_populate_features (fontchooser);
#endif
gtk_font_chooser_widget_set_cell_size (fontchooser);
@ -1475,7 +1490,7 @@ gtk_font_chooser_widget_ensure_selection (GtkFontChooserWidget *fontchooser)
}
}
#if defined(HAVE_HARFBUZZ) && defined(HAVE_PANGOFT)
#ifdef HAVE_FONT_FEATURES
/* OpenType variations */
@ -1496,12 +1511,15 @@ add_font_variations (GtkFontChooserWidget *fontchooser,
char tag[5];
double value;
value = gtk_adjustment_get_value (axis->adjustment);
if (value == axis->default_value)
continue;
tag[0] = (axis->tag >> 24) & 0xff;
tag[1] = (axis->tag >> 16) & 0xff;
tag[2] = (axis->tag >> 8) & 0xff;
tag[3] = (axis->tag >> 0) & 0xff;
tag[4] = '\0';
value = gtk_adjustment_get_value (axis->adjustment);
g_string_append_printf (s, "%s%s=%s", sep, tag, g_ascii_dtostr (buf, sizeof(buf), value));
sep = ",";
}
@ -1521,18 +1539,16 @@ adjustment_changed (GtkAdjustment *adjustment,
s = g_string_new ("");
add_font_variations (fontchooser, s);
if (s->len > 0)
{
font_desc = pango_font_description_new ();
pango_font_description_set_variations (font_desc, s->str);
gtk_font_chooser_widget_take_font_desc (fontchooser, font_desc);
}
font_desc = pango_font_description_new ();
pango_font_description_set_variations (font_desc, s->str);
gtk_font_chooser_widget_take_font_desc (fontchooser, font_desc);
g_string_free (s, TRUE);
priv->updating_variations = FALSE;
}
#ifdef FONT_FEATURES_USE_PANGOFT2
static gboolean
should_show_axis (FT_Var_Axis *ax)
{
@ -1549,22 +1565,149 @@ is_named_instance (FT_Face face)
return (face->face_index >> 16) > 0;
}
#define TAG_WIDTH FT_MAKE_TAG ('w', 'd', 't', 'h')
#define TAG_WEIGHT FT_MAKE_TAG ('w', 'g', 'h', 't')
#define TAG_ITALIC FT_MAKE_TAG ('i', 't', 'a', 'l')
#define TAG_SLANT FT_MAKE_TAG ('s', 'l', 'n', 't')
#define TAG_OPTICAL_SIZE FT_MAKE_TAG ('o', 'p', 's', 'z')
#else
static gboolean
should_show_axis (hb_ot_var_axis_info_t *ax)
{
if (ax->flags & HB_OT_VAR_AXIS_FLAG_HIDDEN)
return FALSE;
return TRUE;
}
static gboolean
is_named_instance (hb_face_t *face)
{
/* FIXME */
return FALSE;
}
#define TAG_WIDTH HB_OT_TAG_VAR_AXIS_WIDTH
#define TAG_WEIGHT HB_OT_TAG_VAR_AXIS_WEIGHT
#define TAG_ITALIC HB_OT_TAG_VAR_AXIS_ITALIC
#define TAG_SLANT HB_OT_TAG_VAR_AXIS_SLANT
#define TAG_OPTICAL_SIZE HB_OT_TAG_VAR_AXIS_OPTICAL_SIZE
#endif
static struct {
guint32 tag;
const char *name;
} axis_names[] = {
{ FT_MAKE_TAG ('w', 'd', 't', 'h'), N_("Width") },
{ FT_MAKE_TAG ('w', 'g', 'h', 't'), N_("Weight") },
{ FT_MAKE_TAG ('i', 't', 'a', 'l'), N_("Italic") },
{ FT_MAKE_TAG ('s', 'l', 'n', 't'), N_("Slant") },
{ FT_MAKE_TAG ('o', 'p', 's', 'z'), N_("Optical Size") },
{ TAG_WIDTH, N_("Width") },
{ TAG_WEIGHT, N_("Weight") },
{ TAG_ITALIC, N_("Italic") },
{ TAG_SLANT, N_("Slant") },
{ TAG_OPTICAL_SIZE, N_("Optical Size") },
};
#undef TAG_WIDTH
#undef TAG_WEIGHT
#undef TAG_ITALIC
#undef TAG_SLANT
#undef TAG_OPTICAL_SIZE
#ifdef FONT_FEATURES_USE_PANGOFT2
#define FONT_FACE_TYPE FT_Face
#define FONT_VAR_AXIS_TYPE FT_Var_Axis
#define FONT_VALUE_TYPE FT_Fixed
/*
* We actually don't bother about the FT_Face here, but we use this so that we can have a single
* version of add_axis() taylored to PangoFT2 or Pango with HarfBuzz integrated
*/
static void *
get_font_name (FT_Face face,
FT_Var_Axis *ax,
const char *result)
{
result = ax->name;
}
static const float
get_float_value (FT_Fixed value)
{
return FixedToFloat (value);
}
static const float
get_axis_float_max (FT_Var_Axis *ax)
{
return FixedToFloat (ax->maximum);
}
static const float
get_axis_float_min (FT_Var_Axis *ax)
{
return FixedToFloat (ax->minimum);
}
static const float
get_axis_float_default (FT_Var_Axis *ax)
{
return FixedToFloat (ax->def);
}
#else
#define FONT_FACE_TYPE hb_face_t *
#define FONT_VAR_AXIS_TYPE hb_ot_var_axis_info_t
#define FONT_VALUE_TYPE int
static void
get_font_name (hb_face_t *face,
hb_ot_var_axis_info_t *ax,
const char *name)
{
char buffer[20];
unsigned int buffer_len = 20;
hb_ot_name_get_utf8 (face, ax->name_id, HB_LANGUAGE_INVALID, &buffer_len, buffer);
name = buffer;
}
#define get_float_value(x) x
static const float
get_axis_float_max (hb_ot_var_axis_info_t *ax)
{
return ax->max_value;
}
static const float
get_axis_float_min (hb_ot_var_axis_info_t *ax)
{
return ax->min_value;
}
static const float
get_axis_float_default (hb_ot_var_axis_info_t *ax)
{
return ax->default_value;
}
/* FIXME: This doesn't work if the font has an avar table */
static float
denorm_coord (hb_ot_var_axis_info_t *axis, int coord)
{
float r = coord / 16384.0;
if (coord < 0)
return axis->default_value + r * (axis->default_value - axis->min_value);
else
return axis->default_value + r * (axis->max_value - axis->default_value);
}
#endif
static gboolean
add_axis (GtkFontChooserWidget *fontchooser,
FT_Face face,
FT_Var_Axis *ax,
FT_Fixed value,
FONT_FACE_TYPE face,
FONT_VAR_AXIS_TYPE *ax,
FONT_VALUE_TYPE value,
int row)
{
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
@ -1575,8 +1718,10 @@ add_axis (GtkFontChooserWidget *fontchooser,
axis = g_new (Axis, 1);
axis->tag = ax->tag;
axis->fontchooser = GTK_WIDGET (fontchooser);
axis->default_value = get_axis_float_default (ax);
get_font_name (face, ax, name);
name = ax->name;
for (i = 0; i < G_N_ELEMENTS (axis_names); i++)
{
if (axis_names[i].tag == ax->tag)
@ -1585,18 +1730,19 @@ add_axis (GtkFontChooserWidget *fontchooser,
break;
}
}
axis->label = gtk_label_new (name);
gtk_widget_show (axis->label);
gtk_widget_set_halign (axis->label, GTK_ALIGN_START);
gtk_widget_set_valign (axis->label, GTK_ALIGN_BASELINE);
gtk_grid_attach (GTK_GRID (priv->axis_grid), axis->label, 0, row, 1, 1);
axis->adjustment = gtk_adjustment_new ((double)FixedToFloat(value),
(double)FixedToFloat(ax->minimum),
(double)FixedToFloat(ax->maximum),
axis->adjustment = gtk_adjustment_new ((double)get_float_value (value),
(double)get_axis_float_min (ax),
(double)get_axis_float_max (ax),
1.0, 10.0, 0.0);
axis->scale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, axis->adjustment);
gtk_widget_show (axis->scale);
gtk_scale_add_mark (GTK_SCALE (axis->scale), (double)FixedToFloat(ax->def), GTK_POS_TOP, NULL);
gtk_scale_add_mark (GTK_SCALE (axis->scale), (double)get_axis_float_default (ax), GTK_POS_TOP, NULL);
gtk_widget_set_valign (axis->scale, GTK_ALIGN_BASELINE);
gtk_widget_set_hexpand (axis->scale, TRUE);
gtk_widget_set_size_request (axis->scale, 100, -1);
@ -1612,6 +1758,7 @@ add_axis (GtkFontChooserWidget *fontchooser,
adjustment_changed (axis->adjustment, axis);
g_signal_connect (axis->adjustment, "value-changed", G_CALLBACK (adjustment_changed), axis);
if (is_named_instance (face) || !should_show_axis (ax))
{
gtk_widget_hide (axis->label);
@ -1629,9 +1776,21 @@ gtk_font_chooser_widget_update_font_variations (GtkFontChooserWidget *fontchoose
{
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
PangoFont *pango_font;
#ifdef FONT_FEATURES_USE_PANGOFT2
FT_Face ft_face;
FT_MM_Var *ft_mm_var;
FT_Error ret;
#else
hb_font_t *hb_font;
hb_face_t *hb_face;
const int *coords;
unsigned int n_coords;
hb_ot_var_axis_info_t *axes;
int num_axes, i;
#endif
gboolean has_axis = FALSE;
if (priv->updating_variations)
@ -1645,39 +1804,72 @@ gtk_font_chooser_widget_update_font_variations (GtkFontChooserWidget *fontchoose
pango_font = pango_context_load_font (gtk_widget_get_pango_context (GTK_WIDGET (fontchooser)),
priv->font_desc);
ft_face = pango_fc_font_lock_face (PANGO_FC_FONT (pango_font));
ret = FT_Get_MM_Var (ft_face, &ft_mm_var);
if (ret == 0)
#ifdef FONT_FEATURES_USE_PANGOFT2
if (PANGO_IS_FC_FONT (pango_font))
{
int i;
FT_Fixed *coords;
ft_face = pango_fc_font_lock_face (PANGO_FC_FONT (pango_font));
coords = g_new (FT_Fixed, ft_mm_var->num_axis);
for (i = 0; i < ft_mm_var->num_axis; i++)
coords[i] = ft_mm_var->axis[i].def;
if (ft_face->face_index > 0)
ret = FT_Get_MM_Var (ft_face, &ft_mm_var);
if (ret == 0)
{
int instance_id = ft_face->face_index >> 16;
if (instance_id && instance_id <= ft_mm_var->num_namedstyles)
int i;
FT_Fixed *coords;
coords = g_new (FT_Fixed, ft_mm_var->num_axis);
for (i = 0; i < ft_mm_var->num_axis; i++)
coords[i] = ft_mm_var->axis[i].def;
if (ft_face->face_index > 0)
{
FT_Var_Named_Style *instance = &ft_mm_var->namedstyle[instance_id - 1];
memcpy (coords, instance->coords, ft_mm_var->num_axis * sizeof (*coords));
int instance_id = ft_face->face_index >> 16;
if (instance_id && instance_id <= ft_mm_var->num_namedstyles)
{
FT_Var_Named_Style *instance = &ft_mm_var->namedstyle[instance_id - 1];
memcpy (coords, instance->coords, ft_mm_var->num_axis * sizeof (*coords));
}
}
for (i = 0; i < ft_mm_var->num_axis; i++)
{
if (add_axis (fontchooser, ft_face, &ft_mm_var->axis[i], coords[i], i + 4))
has_axis = TRUE;
}
g_free (coords);
free (ft_mm_var);
}
for (i = 0; i < ft_mm_var->num_axis; i++)
{
if (add_axis (fontchooser, ft_face, &ft_mm_var->axis[i], coords[i], i + 4))
has_axis = TRUE;
}
pango_fc_font_unlock_face (PANGO_FC_FONT (pango_font));
}
#else
hb_font = pango_font_get_hb_font (pango_font);
hb_face = hb_font_get_face (hb_font);
g_free (coords);
free (ft_mm_var);
if (!hb_ot_var_has_data (hb_face))
return FALSE;
coords = hb_font_get_var_coords_normalized (hb_font, &n_coords);
num_axes = hb_ot_var_get_axis_count (hb_face);
axes = g_new0 (hb_ot_var_axis_info_t, num_axes);
hb_ot_var_get_axis_infos (hb_face, 0, &num_axes, axes);
for (i = 0; i < num_axes; i ++)
{
float value;
if (coords && i < n_coords)
value = denorm_coord (&axes[i], coords[i]);
else
value = axes[i].default_value;
if (add_axis (fontchooser, hb_font_get_face (hb_font), &axes[i], value, i + 4))
has_axis = TRUE;
}
pango_fc_font_unlock_face (PANGO_FC_FONT (pango_font));
g_free (axes);
#endif
g_object_unref (pango_font);
return has_axis;
@ -1807,6 +1999,7 @@ feat_pressed (GtkGesture *gesture,
static char *
find_affected_text (hb_tag_t feature_tag,
hb_font_t *hb_font,
hb_face_t *hb_face,
hb_tag_t script_tag,
hb_tag_t lang_tag,
@ -1820,7 +2013,11 @@ find_affected_text (hb_tag_t feature_tag,
chars = g_string_new ("");
hb_ot_layout_table_find_script (hb_face, HB_OT_TAG_GSUB, script_tag, &script_index);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
hb_ot_layout_script_find_language (hb_face, HB_OT_TAG_GSUB, script_index, lang_tag, &lang_index);
G_GNUC_END_IGNORE_DEPRECATIONS
if (hb_ot_layout_language_find_feature (hb_face, HB_OT_TAG_GSUB, script_index, lang_index, feature_tag, &feature_index))
{
unsigned int lookup_indexes[32];
@ -1840,8 +2037,8 @@ find_affected_text (hb_tag_t feature_tag,
hb_set_t* glyphs_input = NULL;
hb_set_t* glyphs_after = NULL;
hb_set_t* glyphs_output = NULL;
hb_font_t *hb_font = NULL;
hb_codepoint_t gid;
gboolean destroy_font = FALSE;
glyphs_input = hb_set_create ();
@ -1854,8 +2051,16 @@ find_affected_text (hb_tag_t feature_tag,
glyphs_after,
glyphs_output);
hb_font = hb_font_create (hb_face);
hb_ft_font_set_funcs (hb_font);
#ifdef FONT_FEATURES_USE_PANGOFT
if (hb_font == NULL)
{
/* only applicable if we are doing this via PangoFT2, where we need to create the hb_font_t */
hb_font = hb_font_create (hb_face);
hb_ft_font_set_funcs (hb_font);
destroy_font = TRUE;
}
#endif
gid = -1;
while (hb_set_next (glyphs_input, &gid)) {
@ -1876,7 +2081,9 @@ find_affected_text (hb_tag_t feature_tag,
}
}
hb_set_destroy (glyphs_input);
hb_font_destroy (hb_font);
if (destroy_font)
hb_font_destroy (hb_font);
}
}
@ -1885,6 +2092,7 @@ find_affected_text (hb_tag_t feature_tag,
static void
update_feature_example (FeatureItem *item,
hb_font_t *hb_font,
hb_face_t *hb_face,
hb_tag_t script_tag,
hb_tag_t lang_tag,
@ -1937,9 +2145,9 @@ update_feature_example (FeatureItem *item,
else if (strcmp (item->name, "zero") == 0)
input = g_strdup ("0");
else if (strcmp (item->name, "nalt") == 0)
input = find_affected_text (item->tag, hb_face, script_tag, lang_tag, 3);
input = find_affected_text (item->tag, hb_font, hb_face, script_tag, lang_tag, 3);
else
input = find_affected_text (item->tag, hb_face, script_tag, lang_tag, 10);
input = find_affected_text (item->tag, hb_font, hb_face, script_tag, lang_tag, 10);
if (input[0] != '\0')
{
@ -2156,8 +2364,12 @@ gtk_font_chooser_widget_update_font_features (GtkFontChooserWidget *fontchooser)
{
GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
PangoFont *pango_font;
#ifdef FONT_FEATURES_USE_PANGOFT2
FT_Face ft_face;
hb_font_t *hb_font;
#endif
hb_font_t *hb_font = NULL;
hb_tag_t script_tag;
hb_tag_t lang_tag;
guint script_index = 0;
@ -2165,6 +2377,7 @@ gtk_font_chooser_widget_update_font_features (GtkFontChooserWidget *fontchooser)
int i, j;
GList *l;
gboolean has_feature = FALSE;
gboolean cleanup_hb_face = FALSE;
for (l = priv->feature_items; l; l = l->next)
{
@ -2178,8 +2391,17 @@ gtk_font_chooser_widget_update_font_features (GtkFontChooserWidget *fontchooser)
pango_font = pango_context_load_font (gtk_widget_get_pango_context (GTK_WIDGET (fontchooser)),
priv->font_desc);
ft_face = pango_fc_font_lock_face (PANGO_FC_FONT (pango_font)),
hb_font = hb_ft_font_create (ft_face, NULL);
#ifdef FONT_FEATURE_USE_PANGOFT2
if (PANGO_IS_FC_FONT (pango_font))
{
ft_face = pango_fc_font_lock_face (PANGO_FC_FONT (pango_font)),
hb_font = hb_ft_font_create (ft_face, NULL);
cleanup_hb_face = TRUE;
}
#else
hb_font = pango_font_get_hb_font (pango_font);
#endif
if (hb_font)
{
@ -2197,7 +2419,11 @@ gtk_font_chooser_widget_update_font_features (GtkFontChooserWidget *fontchooser)
for (i = 0; i < 2; i++)
{
hb_ot_layout_table_find_script (hb_face, table[i], script_tag, &script_index);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
hb_ot_layout_script_find_language (hb_face, table[i], script_index, lang_tag, &lang_index);
G_GNUC_END_IGNORE_DEPRECATIONS
count = G_N_ELEMENTS (features);
hb_ot_layout_language_get_feature_tags (hb_face,
table[i],
@ -2214,6 +2440,8 @@ gtk_font_chooser_widget_update_font_features (GtkFontChooserWidget *fontchooser)
for (l = priv->feature_items; l; l = l->next)
{
FeatureItem *item = l->data;
hb_font_t *hb_font2 = NULL;
if (item->tag != features[j])
continue;
@ -2221,7 +2449,10 @@ gtk_font_chooser_widget_update_font_features (GtkFontChooserWidget *fontchooser)
gtk_widget_show (item->top);
gtk_widget_show (gtk_widget_get_parent (item->top));
update_feature_example (item, hb_face, script_tag, lang_tag, priv->font_desc);
if (!cleanup_hb_face)
hb_font2 = hb_font;
update_feature_example (item, hb_font2, hb_face, script_tag, lang_tag, priv->font_desc);
if (GTK_IS_RADIO_BUTTON (item->feat))
{
@ -2235,10 +2466,14 @@ gtk_font_chooser_widget_update_font_features (GtkFontChooserWidget *fontchooser)
}
}
hb_face_destroy (hb_face);
if (cleanup_hb_face)
hb_face_destroy (hb_face);
}
pango_fc_font_unlock_face (PANGO_FC_FONT (pango_font));
#if FONT_FEATURE_USE_PANGOFT2
if (PANGO_IS_FC_FONT (pango_font))
pango_fc_font_unlock_face (PANGO_FC_FONT (pango_font));
#endif
g_object_unref (pango_font);
return has_feature;
@ -2338,7 +2573,7 @@ gtk_font_chooser_widget_merge_font_desc (GtkFontChooserWidget *fontchooser
gtk_font_chooser_widget_update_marks (fontchooser);
#if defined(HAVE_HARFBUZZ) && defined(HAVE_PANGOFT)
#ifdef HAVE_FONT_FEATURES
if (gtk_font_chooser_widget_update_font_features (fontchooser))
has_tweak = TRUE;
if (gtk_font_chooser_widget_update_font_variations (fontchooser))
@ -2348,6 +2583,14 @@ gtk_font_chooser_widget_merge_font_desc (GtkFontChooserWidget *fontchooser
g_simple_action_set_enabled (G_SIMPLE_ACTION (priv->tweak_action), has_tweak);
}
#ifdef HAVE_FONT_FEATURES
if (mask & PANGO_FONT_MASK_VARIATIONS)
{
if (pango_font_description_get_variations (priv->font_desc)[0] == '\0')
pango_font_description_unset_fields (priv->font_desc, PANGO_FONT_MASK_VARIANT);
}
#endif
gtk_font_chooser_widget_update_preview_attributes (fontchooser);
g_object_notify (G_OBJECT (fontchooser), "font");

View File

@ -49,7 +49,7 @@ EXTRA_DIST += \
replace.py \
pc_base.py \
gtkpc.py \
README_EGL_MSVC.txt \
README_FEATURES_MSVC.txt \
$(GENERATED_ITEMS)
-include $(top_srcdir)/git.mk

View File

@ -1,29 +0,0 @@
Notes on enabling EGL (ANGLE/D3D support) for Windows/Visual Studio builds
==========================================================================
There is now support in the GL context creation code for Windows in GDK for
creating and using EGL (OpenGL ES 3) contexts, which can be used instead of
the existing OpenGL (Desktop) support, especially when the graphics drivers
do not support OpenGL adequately.
This support is not enabled by default in the project files. In order to do
so, please do the following:
-Obtain or compile a build of recent version of ANGLE. The one that comes
with QT 5.10.x is sufficiently recent, but not the one that comes with QT-
5.6.x. Note that Visual Studio 2013 or later is required for building
ANGLE from QT-5.10.x, but the Visual Studio 2013-built ANGLE DLLs does work
without problems with GTK+ built with Visual Studio 2008~2013. You may
need to obtain D3Dcompiler_[47|43|42].dll if it does not come with the
system (which is part of the DirectX runtimes). Its headers and .lib
needs to be set to be found by the compiler and linker respectively before
building libepoxy.
-Build libepoxy with EGL support, which has to be enabled explicitly on
Windows builds. Pass in -Degl=yes when building libepoxy using Meson.
Build and install, making sure the headers and .lib can be located by the
compiler and linker respectively.
-Open the vsX/gtk+.sln, and open the project properties in the "gdk3-win32"
project. Under "C/C++", add GDK_WIN32_ENABLE_EGL in the "Preprocessor
Definitions" to the existing definitions in there for the configuration
that is being built. Then build the solution.
-To force the use of the EGL code, set the envvar GDK_GL=(...,)gles , where (...,)
are the other GDK_GL options desired.

View File

@ -0,0 +1,72 @@
Preameble
=========
This file attempts to give further info about how to enable features
that are not available in the Visual Studio project files shipped
with the source release archive, i.e. beyond building GTK with the GDK
Win32 backend, with or without the Broadway GDK backend.
The following also apply to Visual Studio builds done with Meson in terms
of getting the required dependencies for the optional features.
==========================================================================
Notes on enabling EGL (ANGLE/D3D support) for Windows/Visual Studio builds
==========================================================================
There is now support in the GL context creation code for Windows in GDK for
creating and using EGL (OpenGL ES 3) contexts, which can be used instead of
the existing OpenGL (Desktop) support, especially when the graphics drivers
do not support OpenGL adequately.
This support is not enabled by default in the project files. In order to do
so, please do the following:
-Obtain or compile a build of recent version of ANGLE. The one that comes
with QT 5.10.x is sufficiently recent, but not the one that comes with QT-
5.6.x. Note that Visual Studio 2013 or later is required for building
ANGLE from QT-5.10.x, but the Visual Studio 2013-built ANGLE DLLs does
work without problems with GTK+ built with Visual Studio 2008~2013.
You may need to obtain D3Dcompiler_[47|43|42].dll if it does not come
with the system (which is part of the DirectX runtimes). Visual Studio
2015 or later can use ANGLE from QT 5.11.x or later, or from Google's
GIT repos, which may require later version of Visual Studio to build.
Its headers and .lib needs to be set to be found by the compiler and
linker respectively before building libepoxy.
-Build libepoxy with EGL support, which has to be enabled explicitly on
Windows builds. Pass in -Degl=yes when building libepoxy using Meson.
Build and install, making sure the headers and .lib can be located by the
compiler and linker respectively.
-Open the vsX/gtk+.sln, and open the project properties in the "gdk3-win32"
project. Under "C/C++", add GDK_WIN32_ENABLE_EGL in the "Preprocessor
Definitions" to the existing definitions in there for the configuration
that is being built. Then build the solution.
-To force the use of the EGL code, set the envvar GDK_GL=(...,)gles ,
where (...,) are the other GDK_GL options desired.
==============================================================
Enabling the font tweaking features and the font features demo
==============================================================
The font tweaking features in the GTK DLL is enabled automatically if
the Pango 1.44.0 and HarfBuzz 2.2.0 (or later) headers and libraries
(and hence DLLs) are found during compile time. Check in
gtkfontchooserwidget.c that the `#pragma comment(lib, "harfbuzz")` line
to ensure that you have your HarfBuzz .lib file named as such, which
is the default .lib name for HarfBuzz builds.
Alternatively, they can be manually enabled by making sure that
`HAVE_HARFBUZZ` and `HAVE_PANGOFT2` are defined in config.h.win32,
meaning that PangoFT2 must be present, which depends on HarfBuzz,
FontConfig and FreeType. You will then need to add to the `gtk3`
projects the .lib's of PangoFT2, HarfBuzz and FreeType in the
"Additional Libraries" entry under the linker settings.
Please note that the font features demo is not built into gtk3-demo
by default. To do that, run in a Visual Studio command prompt, go to
$(srcroot)\win32, and run
"nmake /f generate-msvc.mak regenerate-demos-h-win32 FONT_FEATURES_DEMO=1".
To undo that, run that command without "FONT_FEATURES_DEMO=1". Python must
be present in your PATH or passed in via PYTHON=<path_to_python_interpreter>.
You will then need to add $(srcroot)\demos\gtk-demo\font_features.c into
the source list of the "gtk3-demo" project, and add the .lib file for
HarfBuzz, and if using pre-Pango 1.44.0 and/or pre-HarfBuzz 2.2.0, the
.lib files for PangoFT2 and FreeType into the "Additional Libraries"
entry under the linker settings.

View File

@ -46,6 +46,11 @@ AT_PLAT=aarch64
AT_PLAT=i686
!endif
demo_sources = $(demos_base)
!ifdef FONT_FEATURES_DEMO
demo_sources = $(demo_sources) $(font_features_demo)
!endif
# Please do not change anything beneath this line unless maintaining the NMake Makefiles
GTK_VERSION = @GTK_VERSION@

View File

@ -99,3 +99,17 @@ NULL=
!if [del /f /q resources_sources.mak]
!endif
!if [call create-lists.bat header demo_sources.mak demo_actual_sources]
!endif
!if [for %f in ($(demo_sources)) do @call create-lists.bat file demo_sources.mak ..\demos\gtk-demo\%f]
!endif
!if [call create-lists.bat footer demo_sources.mak]
!endif
!include demo_sources.mak
!if [del /f /q demo_sources.mak]
!endif

View File

@ -3,6 +3,7 @@
# Items in here should not need to be edited unless
# one is maintaining the NMake build files.
!include ../demos/gtk-demo/demos-sources.mak
!include config-msvc.mak
!include create-lists-msvc.mak
@ -193,6 +194,12 @@ all: \
@echo Generating $@...
@$(GLIB_COMPILE_RESOURCES) --target=$@ --sourcedir=$(@D) --generate-source $(@D)\iconbrowser.gresource.xml
regenerate-demos-h-win32: ..\demos\gtk-demo\geninclude.py $(demo_actual_sources)
@echo Regenerating demos.h.win32...
@-del ..\demos\gtk-demo\demos.h.win32
@cd ..\demos\gtk-demo
@$(PYTHON) geninclude.py demos.h.win32 $(demo_sources)
# Remove the generated files
clean:
@-del /f /q ..\demos\icon-browser\resources.c

View File

@ -12,7 +12,7 @@
<GtkHostMachine Condition="'$(Platform)' == 'Win32'">i686</GtkHostMachine>
<GtkHostMachine Condition="'$(Platform)' == 'x64'">x86_64</GtkHostMachine>
<GtkHostMachine Condition="'$(Platform)' == 'arm64'">aarch64</GtkHostMachine>
<GtkDefines>GTK_COMPILATION;G_LOG_DOMAIN="Gtk";GTK_HOST="$(GtkHostMachine)-pc-vs$(VSVer)";GTK_PRINT_BACKENDS="file";GTK_PRINT_BACKEND_ENABLE_UNSUPPORTED;$(GtkIncludedImmodulesDefines);GTK_LIBDIR="$(GtkDummyPrefix)/lib";GTK_DATADIR="$(GtkDummyPrefix)/share";GTK_DATA_PREFIX="$(GtkDummyPrefix)";GTK_SYSCONFDIR="$(GtkDummyPrefix)/etc";MULTIPRESS_CONFDIR="$(GtkDummyPrefix)/etc/gtk-$(ApiVersion)";MULTIPRESS_LOCALEDIR="$(GtkDummyPrefix)/share/locale";GTK_VERSION="$(GtkVersion)/etc";GTK_BINARY_VERSION="$(GtkBinaryVersion)/etc";GDK_DISABLE_DEPRECATED;ISOLATION_AWARE_ENABLED</GtkDefines>
<GtkDefines>GTK_COMPILATION;G_LOG_DOMAIN="Gtk";GTK_HOST="$(GtkHostMachine)-pc-vs$(VSVer)";GTK_PRINT_BACKENDS="file";GTK_PRINT_BACKEND_ENABLE_UNSUPPORTED;$(GtkIncludedImmodulesDefines);GTK_LIBDIR="$(GtkDummyPrefix)/lib";GTK_DATADIR="$(GtkDummyPrefix)/share";GTK_DATA_PREFIX="$(GtkDummyPrefix)";GTK_SYSCONFDIR="$(GtkDummyPrefix)/etc";MULTIPRESS_CONFDIR="$(GtkDummyPrefix)/etc/gtk-$(ApiVersion)";MULTIPRESS_LOCALEDIR="$(GtkDummyPrefix)/share/locale";GTK_VERSION="$(GtkVersion)/etc";GTK_BINARY_VERSION="$(GtkBinaryVersion)/etc";GDK_DISABLE_DEPRECATED;ISOLATION_AWARE_ENABLED;__MSVC_PROJECTS__</GtkDefines>
<CommonARM64SystemLibs Condition="'$(VisualStudioVersion)|$(Platform)' == '15.0|arm64'">ole32.lib;advapi32.lib;shell32.lib;gdi32.lib</CommonARM64SystemLibs>
<GtkGdkCommonLibs>pangowin32-1.0.lib;fribidi.lib;imm32.lib;$(CommonARM64SystemLibs)</GtkGdkCommonLibs>
<GdkAdditionalLibs>winmm.lib;dwmapi.lib;setupapi.lib;hid.lib;$(GtkGdkCommonLibs)</GdkAdditionalLibs>

View File

@ -41,11 +41,11 @@
/>
<UserMacro
Name="GtkDefines"
Value="GTK_COMPILATION;G_LOG_DOMAIN=\&quot;Gtk\&quot;;GTK_HOST=\&quot;i686-pc-vs$(VSVer)\&quot;;GTK_PRINT_BACKENDS=\&quot;file\&quot;;GTK_PRINT_BACKEND_ENABLE_UNSUPPORTED;$(GtkIncludedImmodulesDefines);GTK_LIBDIR=\&quot;$(GtkDummyPrefix)/lib\&quot;;GTK_DATADIR=\&quot;$(GtkDummyPrefix)/share\&quot;GTK_DATA_PREFIX=\&quot;$(GtkDummyPrefix)\&quot;;GTK_SYSCONFDIR=\&quot;$(GtkDummyPrefix)/etc\&quot;;MULTIPRESS_CONFDIR=\&quot;$(GtkDummyPrefix)/etc/gtk-$(ApiVersion)\&quot;;MULTIPRESS_LOCALEDIR=\&quot;$(GtkDummyPrefix)/share/locale\&quot;;GTK_VERSION=\&quot;$(GtkVersion)/etc\&quot;;GTK_BINARY_VERSION=\&quot;$(GtkBinaryVersion)/etc\&quot;;GDK_DISABLE_DEPRECATED;ISOLATION_AWARE_ENABLED"
Value="GTK_COMPILATION;G_LOG_DOMAIN=\&quot;Gtk\&quot;;GTK_HOST=\&quot;i686-pc-vs$(VSVer)\&quot;;GTK_PRINT_BACKENDS=\&quot;file\&quot;;GTK_PRINT_BACKEND_ENABLE_UNSUPPORTED;$(GtkIncludedImmodulesDefines);GTK_LIBDIR=\&quot;$(GtkDummyPrefix)/lib\&quot;;GTK_DATADIR=\&quot;$(GtkDummyPrefix)/share\&quot;GTK_DATA_PREFIX=\&quot;$(GtkDummyPrefix)\&quot;;GTK_SYSCONFDIR=\&quot;$(GtkDummyPrefix)/etc\&quot;;MULTIPRESS_CONFDIR=\&quot;$(GtkDummyPrefix)/etc/gtk-$(ApiVersion)\&quot;;MULTIPRESS_LOCALEDIR=\&quot;$(GtkDummyPrefix)/share/locale\&quot;;GTK_VERSION=\&quot;$(GtkVersion)/etc\&quot;;GTK_BINARY_VERSION=\&quot;$(GtkBinaryVersion)/etc\&quot;;GDK_DISABLE_DEPRECATED;ISOLATION_AWARE_ENABLED;__MSVC_PROJECTS__"
/>
<UserMacro
Name="GtkDefinesX64"
Value="GTK_COMPILATION;G_LOG_DOMAIN=\&quot;Gtk\&quot;;GTK_HOST=\&quot;x86_64-pc-vs$(VSVer)\&quot;;GTK_PRINT_BACKENDS=\&quot;file\&quot;;GTK_PRINT_BACKEND_ENABLE_UNSUPPORTED;$(GtkIncludedImmodulesDefines);GTK_LIBDIR=\&quot;$(GtkDummyPrefix)/lib\&quot;;GTK_DATADIR=\&quot;$(GtkDummyPrefix)/share\&quot;GTK_DATA_PREFIX=\&quot;$(GtkDummyPrefix)\&quot;;GTK_SYSCONFDIR=\&quot;$(GtkDummyPrefix)/etc\&quot;;MULTIPRESS_CONFDIR=\&quot;$(GtkDummyPrefix)/etc/gtk-$(ApiVersion)\&quot;;MULTIPRESS_LOCALEDIR=\&quot;$(GtkDummyPrefix)/share/locale\&quot;;GTK_VERSION=\&quot;$(GtkVersion)/etc\&quot;;GTK_BINARY_VERSION=\&quot;$(GtkBinaryVersion)/etc\&quot;;GDK_DISABLE_DEPRECATED;ISOLATION_AWARE_ENABLED"
Value="GTK_COMPILATION;G_LOG_DOMAIN=\&quot;Gtk\&quot;;GTK_HOST=\&quot;x86_64-pc-vs$(VSVer)\&quot;;GTK_PRINT_BACKENDS=\&quot;file\&quot;;GTK_PRINT_BACKEND_ENABLE_UNSUPPORTED;$(GtkIncludedImmodulesDefines);GTK_LIBDIR=\&quot;$(GtkDummyPrefix)/lib\&quot;;GTK_DATADIR=\&quot;$(GtkDummyPrefix)/share\&quot;GTK_DATA_PREFIX=\&quot;$(GtkDummyPrefix)\&quot;;GTK_SYSCONFDIR=\&quot;$(GtkDummyPrefix)/etc\&quot;;MULTIPRESS_CONFDIR=\&quot;$(GtkDummyPrefix)/etc/gtk-$(ApiVersion)\&quot;;MULTIPRESS_LOCALEDIR=\&quot;$(GtkDummyPrefix)/share/locale\&quot;;GTK_VERSION=\&quot;$(GtkVersion)/etc\&quot;;GTK_BINARY_VERSION=\&quot;$(GtkBinaryVersion)/etc\&quot;;GDK_DISABLE_DEPRECATED;ISOLATION_AWARE_ENABLED;__MSVC_PROJECTS__"
/>
<UserMacro
Name="GtkGdkCommonLibs"