Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
font-variations.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Author:
4 * Felipe Corrêa da Silva Sanches <juca@members.fsf.org>
5 * Tavmjong Bah <tavmjong@free.fr>
6 * Michael Kowalski <michal_kowalski@hotmail.com>
7 *
8 * Copyright (C) 2018 Felipe Corrêa da Silva Sanches, Tavmong Bah
9 *
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include <algorithm>
14#include <boost/algorithm/string.hpp>
15#include <cmath>
16#include <glibmm/refptr.h>
17#include <glibmm/ustring.h>
18#include <gtkmm/adjustment.h>
19#include <gtkmm/enums.h>
20#include <gtkmm/object.h>
21#include <gtkmm/sizegroup.h>
22#include <gtkmm/spinbutton.h>
23#include <iostream>
24#include <iomanip>
25#include <map>
26
27#include <gtkmm.h>
28#include <glibmm/i18n.h>
29
31#include <string>
32#include <utility>
34
35#include "font-variations.h"
36
37// For updating from selection
39
40#include "ui/util.h"
41
42namespace Inkscape {
43namespace UI {
44namespace Widget {
45
46std::pair<Glib::ustring, Glib::ustring> get_axis_name(const std::string& tag, const Glib::ustring& abbr) {
47 // Transformed axis names;
48 // mainly from https://fonts.google.com/knowledge/using_type/introducing_parametric_axes
49 // CC BY-SA 4.0
50 // Standard axes guide for reference: https://variationsguide.typenetwork.com
51 static std::map<std::string, std::pair<Glib::ustring, Glib::ustring>> map = {
52 // TRANSLATORS: “Grade” (GRAD in CSS) is an axis that can be used to alter stroke thicknesses (or other forms)
53 // without affecting the type's overall width, inter-letter spacing, or kerning — unlike altering weight.
54 {"GRAD", std::make_pair(C_("Variable font axis", "Grade"), _("Alter stroke thicknesses (or other forms) without affecting the type’s overall width"))},
55 // TRANSLATORS: “Parametric Thick Stroke”, XOPQ, is a reference to its logical name, “X Opaque”,
56 // which describes how it alters the opaque stroke forms of glyphs typically in the X dimension
57 {"XOPQ", std::make_pair(C_("Variable font axis", "X opaque"), _("Alter the opaque stroke forms of glyphs in the X dimension"))},
58 // TRANSLATORS: “Parametric Thin Stroke”, YOPQ, is a reference to its logical name, “Y Opaque”,
59 // which describes how it alters the opaque stroke forms of glyphs typically in the Y dimension
60 {"YOPQ", std::make_pair(C_("Variable font axis", "Y opaque"), _("Alter the opaque stroke forms of glyphs in the Y dimension"))},
61 // TRANSLATORS: “Parametric Counter Width”, XTRA, is a reference to its logical name, “X-Transparent,”
62 // which describes how it alters a font’s transparent spaces (also known as negative shapes)
63 // inside and around all glyphs along the X dimension
64 {"XTRA", std::make_pair(C_("Variable font axis", "X transparent"), _("Alter the transparent spaces inside and around all glyphs along the X dimension"))},
65 {"YTRA", std::make_pair(C_("Variable font axis", "Y transparent"), _("Alter the transparent spaces inside and around all glyphs along the Y dimension"))},
66 // TRANSLATORS: Width/height of Chinese glyphs
67 {"XTCH", std::make_pair(C_("Variable font axis", "X transparent Chinese"), _("Alter the width of Chinese glyphs"))},
68 {"YTCH", std::make_pair(C_("Variable font axis", "Y transparent Chinese"), _("Alter the height of Chinese glyphs"))},
69 // TRANSLATORS: “Parametric Lowercase Height”
70 {"YTLC", std::make_pair(C_("Variable font axis", "Lowercase height"), _("Vary the height of counters and other spaces between the baseline and x-height"))},
71 // TRANSLATORS: “Parametric Uppercase Counter Height”
72 {"YTUC", std::make_pair(C_("Variable font axis", "Uppercase height"), _("Vary the height of uppercase letterforms"))},
73 // TRANSLATORS: “Parametric Ascender Height”
74 {"YTAS", std::make_pair(C_("Variable font axis", "Ascender height"), _("Vary the height of lowercase ascenders"))},
75 // TRANSLATORS: “Parametric Descender Depth”
76 {"YTDE", std::make_pair(C_("Variable font axis", "Descender depth"), _("Vary the depth of lowercase descenders"))},
77 // TRANSLATORS: “Parametric Figure Height”
78 {"YTFI", std::make_pair(C_("Variable font axis", "Figure height"), _("Vary the height of figures"))},
79 // TRANSLATORS: "Serif rise" - found in the wild (https://github.com/googlefonts/amstelvar)
80 {"YTSE", std::make_pair(C_("Variable font axis", "Serif rise"), _("Vary the shape of the serifs"))},
81 // TRANSLATORS: Flare - flaring of the stems
82 {"FLAR", std::make_pair(C_("Variable font axis", "Flare"), _("Controls the flaring of the stems"))},
83 // TRANSLATORS: Volume - The volume axis works only in combination with the Flare axis. It transforms the serifs
84 // and adds a little more edge to details.
85 {"VOLM", std::make_pair(C_("Variable font axis", "Volume"), _("Volume works in combination with flare to transform serifs"))},
86 // Softness
87 {"SOFT", std::make_pair(C_("Variable font axis", "Softness"), _("Softness makes letterforms more soft and rounded"))},
88 // Casual
89 {"CASL", std::make_pair(C_("Variable font axis", "Casual"), _("Adjust the letterforms from a more serious style to a more casual style"))},
90 // Cursive
91 {"CRSV", std::make_pair(C_("Variable font axis", "Cursive"), _("Control the substitution of cursive forms"))},
92 // Fill
93 {"FILL", std::make_pair(C_("Variable font axis", "Fill"), _("Fill can turn transparent forms opaque"))},
94 // Monospace
95 {"MONO", std::make_pair(C_("Variable font axis", "Monospace"), _("Adjust the glyphs from a proportional width to a fixed width"))},
96 // Wonky
97 {"WONK", std::make_pair(C_("Variable font axis", "Wonky"), _("Binary switch used to control substitution of “wonky” forms"))},
98 // Element shape
99 {"ESHP", std::make_pair(C_("Variable font axis", "Element shape"), _("Selection of the base element glyphs are composed of"))},
100 // Element grid
101 {"EGRD", std::make_pair(C_("Variable font axis", "Element grid"), _("Controls how many elements are used per one grid unit"))},
102 // TRANSLATORS: “Optical Size”
103 // Optical sizes in a variable font are different versions of a typeface optimized for use at singular specific sizes,
104 // such as 14 pt or 144 pt. Small (or body) optical sizes tend to have less stroke contrast, more open and wider spacing,
105 // and a taller x-height than those of their large (or display) counterparts.
106 {"opsz", std::make_pair(C_("Variable font axis", "Optical size"), _("Optimize the typeface for use at specific size"))},
107 // TRANSLATORS: Slant controls the font file’s slant parameter for oblique styles.
108 {"slnt", std::make_pair(C_("Variable font axis", "Slant"), _("Controls the font file’s slant parameter for oblique styles"))},
109 // Italic
110 {"ital", std::make_pair(C_("Variable font axis", "Italic"), _("Turns on the font’s italic forms"))},
111 // TRANSLATORS: Weight controls the font file’s weight parameter.
112 {"wght", std::make_pair(C_("Variable font axis", "Weight"), _("Controls the font file’s weight parameter"))},
113 // TRANSLATORS: Width controls the font file’s width parameter.
114 {"wdth", std::make_pair(C_("Variable font axis", "Width"), _("Controls the font file’s width parameter"))},
115 //
116 {"xtab", std::make_pair(C_("Variable font axis", "Tabular width"), _("Controls the tabular width"))},
117 {"udln", std::make_pair(C_("Variable font axis", "Underline"), _("Controls the weight of an underline"))},
118 {"shdw", std::make_pair(C_("Variable font axis", "Shadow"), _("Controls the depth of a shadow"))},
119 {"refl", std::make_pair(C_("Variable font axis", "Reflection"), _("Controls the Y reflection"))},
120 {"otln", std::make_pair(C_("Variable font axis", "Outline"), _("Controls the weight of a font’s outline"))},
121 {"engr", std::make_pair(C_("Variable font axis", "Engrave"), _("Controls the width of an engraving"))},
122 {"embo", std::make_pair(C_("Variable font axis", "Emboss"), _("Controls the depth of an emboss"))},
123 {"rxad", std::make_pair(C_("Variable font axis", "Relative X advance"), _("Controls the relative X advance - horizontal motion of the glyph"))},
124 {"ryad", std::make_pair(C_("Variable font axis", "Relative Y advance"), _("Controls the relative Y advance - vertical motion of the glyph"))},
125 {"rsec", std::make_pair(C_("Variable font axis", "Relative second"), _("Controls the relative second value - as in one second of animation time"))},
126 {"vrot", std::make_pair(C_("Variable font axis", "Rotation"), _("Controls the rotation of the glyph in degrees"))},
127 {"vuid", std::make_pair(C_("Variable font axis", "Unicode variation"), _("Controls the glyph’s unicode ID"))},
128 {"votf", std::make_pair(C_("Variable font axis", "Feature variation"), _("Controls the glyph’s feature variation"))},
129 };
130
131 auto it = map.find(tag);
132 if (it == end(map)) {
133 // try lowercase variants
134 it = map.find(boost::algorithm::to_lower_copy(tag));
135 }
136 if (it == end(map)) {
137 // try uppercase variants
138 it = map.find(boost::algorithm::to_upper_copy(tag));
139 }
140 if (it != end(map)) {
141 return it->second;
142 }
143 else {
144 return std::make_pair(abbr, "");
145 }
146}
147
148FontVariationAxis::FontVariationAxis(Glib::ustring name_, OTVarAxis const &axis, Glib::ustring label_, Glib::ustring tooltip)
149 : Gtk::Box(Gtk::Orientation::HORIZONTAL)
150 , name(std::move(name_))
151{
152 // std::cout << "FontVariationAxis::FontVariationAxis:: "
153 // << " name: " << name
154 // << " min: " << axis.minimum
155 // << " def: " << axis.def
156 // << " max: " << axis.maximum
157 // << " val: " << axis.set_val << std::endl;
158
159 set_spacing(3);
160
161 label = Gtk::make_managed<Gtk::Label>(label_ + ":");
162 label->set_tooltip_text(tooltip);
163 label->set_xalign(0.0f); // left-align
164 append(*label);
165
166 edit = Gtk::make_managed<Gtk::SpinButton>();
167 edit->set_max_width_chars(5);
168 edit->set_valign(Gtk::Align::CENTER);
169 edit->set_margin_top(2);
170 edit->set_margin_bottom(2);
171 edit->set_tooltip_text(tooltip);
172 append(*edit);
173
174 auto magnitude = static_cast<int>(log10(axis.maximum - axis.minimum));
175 precision = 2 - magnitude;
176 if (precision < 0) precision = 0;
177
178 auto adj = Gtk::Adjustment::create(axis.set_val, axis.minimum, axis.maximum);
179 auto step = pow(10.0, -precision);
180 adj->set_step_increment(step);
181 adj->set_page_increment(step * 10.0);
182 edit->set_adjustment(adj);
183 edit->set_digits(precision);
184
185 auto adj_scale = Gtk::Adjustment::create(axis.set_val, axis.minimum, axis.maximum);
186 adj_scale->set_step_increment(step);
187 adj_scale->set_page_increment(step * 10.0);
188 scale = Gtk::make_managed<Gtk::Scale>();
189 scale->set_digits (precision);
190 scale->set_hexpand(true);
191 scale->set_adjustment(adj_scale);
192 scale->get_style_context()->add_class("small-slider");
193 scale->set_draw_value(false);
194 append(*scale);
195
196 // sync slider with spin button
197 g_object_bind_property(adj->gobj(), "value", adj_scale->gobj(), "value", GBindingFlags(G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL));
198
199 def = axis.def; // Default value
200}
201
203 if (get_value() != value) {
204 scale->get_adjustment()->set_value(value);
205 }
206}
207
208// ------------------------------------------------------------- //
209
211 : Gtk::Box(Gtk::Orientation::VERTICAL)
212{
213 // std::cout << "FontVariations::FontVariations" << std::endl;
214 set_name("FontVariations");
215 _size_group = Gtk::SizeGroup::create(Gtk::SizeGroup::Mode::HORIZONTAL);
216 _size_group_edit = Gtk::SizeGroup::create(Gtk::SizeGroup::Mode::HORIZONTAL);
217}
218
219// Update GUI based on query.
220void FontVariations::update(Glib::ustring const &font_spec)
221{
222 auto res = FontFactory::get().FaceFromFontSpecification(font_spec.c_str());
223 const auto& axes = res ? res->get_opentype_varaxes() : std::map<Glib::ustring, OTVarAxis>();
224
225 bool rebuild = false;
226 if (_open_type_axes.size() != axes.size()) {
227 rebuild = true;
228 }
229 else {
230 // compare axes
231 bool identical = std::equal(begin(axes), end(axes), begin(_open_type_axes));
232 // if identical, then there's nothing to do
233 if (identical) return;
234
235 bool same_def = std::equal(begin(axes), end(axes), begin(_open_type_axes), [=](const auto& a, const auto& b){
236 return a.first == b.first && a.second.same_definition(b.second);
237 });
238
239 // different axes definitions?
240 if (!same_def) rebuild = true;
241 }
242
243 auto scoped(_update.block());
244
245 if (rebuild) {
246 // rebuild UI if variable axes definitions have changed
247 build_ui(axes);
248 }
249 else {
250 // update UI in-place, some values are different
251 auto it = begin(axes);
252 for (auto& axis : _axes) {
253 if (it != end(axes) && axis->get_name() == it->first) {
254 const auto eps = 0.00001;
255 if (abs(axis->get_value() - it->second.set_val) > eps) {
256 axis->set_value(it->second.set_val);
257 }
258 }
259 else {
260 g_message("axis definition mismatch '%s'", axis->get_name().c_str());
261 }
262 ++it;
263 }
264 }
265
266 _open_type_axes = axes;
267}
268
269
270void FontVariations::build_ui(const std::map<Glib::ustring, OTVarAxis>& ot_axes) {
271 // remove existing widgets, if any
272 auto children = get_children();
273 for (auto child : children) {
274 if (auto group = dynamic_cast<FontVariationAxis*>(child)) {
275 _size_group->remove_widget(*group->get_label());
276 _size_group_edit->remove_widget(*group->get_editbox());
277 }
278 remove(*child);
279 }
280
281 _axes.clear();
282 // create new widgets
283 for (const auto& a : ot_axes) {
284 // std::cout << "Creating axis: " << a.first << std::endl;
285 auto label_tooltip = get_axis_name(a.second.tag, a.first);
286 auto axis = Gtk::make_managed<FontVariationAxis>(a.first, a.second, label_tooltip.first, label_tooltip.second);
287 _axes.push_back(axis);
288 append(*axis);
289 _size_group->add_widget(*(axis->get_label())); // Keep labels the same width
290 _size_group_edit->add_widget(*axis->get_editbox());
291 axis->get_editbox()->get_adjustment()->signal_value_changed().connect(
292 [this](){ if (!_update.pending()) {_signal_changed.emit();} }
293 );
294 }
295}
296
297#if false
298void
300
301 // Eventually will want to favor using 'font-weight', etc. but at the moment these
302 // can't handle "fractional" values. See CSS Fonts Module Level 4.
303 sp_repr_css_set_property(css, "font-variation-settings", get_css_string().c_str());
304}
305
306Glib::ustring
308
309 Glib::ustring css_string;
310
311 for (auto axis: axes) {
312 Glib::ustring name = axis->get_name();
313
314 // Translate the "named" axes. (Additional names in 'stat' table, may need to handle them.)
315 if (name == "Width") name = "wdth"; // 'font-stretch'
316 if (name == "Weight") name = "wght"; // 'font-weight'
317 if (name == "OpticalSize") name = "opsz"; // 'font-optical-sizing' Can trigger glyph substitution.
318 if (name == "Slant") name = "slnt"; // 'font-style'
319 if (name == "Italic") name = "ital"; // 'font-style' Toggles from Roman to Italic.
320
321 std::stringstream value;
322 value << std::fixed << std::setprecision(axis->get_precision()) << axis->get_value();
323 css_string += "'" + name + "' " + value.str() + "', ";
324 }
325
326 return css_string;
327}
328#endif
329
330Glib::ustring
331FontVariations::get_pango_string(bool include_defaults) const {
332
333 Glib::ustring pango_string;
334
335 if (!_axes.empty()) {
336
337 pango_string += "@";
338
339 for (const auto& axis: _axes) {
340 if (!include_defaults && axis->get_value() == axis->get_def()) continue;
341 Glib::ustring name = axis->get_name();
342
343 // Translate the "named" axes. (Additional names in 'stat' table, may need to handle them.)
344 if (name == "Width") name = "wdth"; // 'font-stretch'
345 if (name == "Weight") name = "wght"; // 'font-weight'
346 if (name == "OpticalSize") name = "opsz"; // 'font-optical-sizing' Can trigger glyph substitution.
347 if (name == "Slant") name = "slnt"; // 'font-style'
348 if (name == "Italic") name = "ital"; // 'font-style' Toggles from Roman to Italic.
349
351 str << std::fixed << std::setprecision(axis->get_precision()) << axis->get_value();
352 pango_string += name + "=" + str.str() + ",";
353 }
354
355 pango_string.erase (pango_string.size() - 1); // Erase last ',' or '@'
356 }
357
358 return pango_string;
359}
360
362 return !_axes.empty();
363}
364
365Glib::RefPtr<Gtk::SizeGroup> FontVariations::get_size_group(int index) {
366 switch (index) {
367 case 0: return _size_group;
368 case 1: return _size_group_edit;
369 default: return Glib::RefPtr<Gtk::SizeGroup>();
370 }
371}
372
373} // namespace Widget
374} // namespace UI
375} // namespace Inkscape
376
377/*
378 Local Variables:
379 mode:c++
380 c-file-style:"stroustrup"
381 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
382 indent-tabs-mode:nil
383 fill-column:99
384 End:
385*/
386// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
gfloat magnitude(const vector_type)
std::shared_ptr< FontInstance > FaceFromFontSpecification(char const *fontSpecification)
A thin wrapper around std::ostringstream, but writing floating point numbers in the format required b...
A widget for a single axis: Label and Slider.
FontVariationAxis(Glib::ustring name, OTVarAxis const &axis, Glib::ustring label, Glib::ustring tooltip)
Glib::RefPtr< Gtk::SizeGroup > _size_group_edit
Glib::RefPtr< Gtk::SizeGroup > _size_group
Glib::RefPtr< Gtk::SizeGroup > get_size_group(int index)
std::map< Glib::ustring, OTVarAxis > _open_type_axes
std::vector< FontVariationAxis * > _axes
Glib::ustring get_pango_string(bool include_defaults=false) const
void build_ui(const std::map< Glib::ustring, OTVarAxis > &axes)
void update(const Glib::ustring &font_spec)
Update GUI.
void fill_css(SPCSSAttr *css)
Fill SPCSSAttr based on settings of buttons.
Glib::ustring get_css_string()
Get CSS String.
static FontFactory & get(Args &&... args)
Definition statics.h:153
double minimum
double maximum
double set_val
double def
scoped_block block()
TODO: insert short description here.
std::shared_ptr< Css const > css
std::unordered_map< std::string, std::unique_ptr< SPDocument > > map
TODO: insert short description here.
The data describing a single loaded font.
Definition desktop.h:50
std::pair< Glib::ustring, Glib::ustring > get_axis_name(const std::string &tag, const Glib::ustring &abbr)
std::vector< Gtk::Widget * > get_children(Gtk::Widget &widget)
Get a vector of the widgetʼs children, from get_first_child() through each get_next_sibling().
Definition util.cpp:156
Helper class to stream background task notifications as a series of messages.
static void append(std::vector< T > &target, std::vector< T > &&source)
STL namespace.
Ocnode * child[8]
Definition quantize.cpp:33
void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
Set a style property to a new value (e.g.
Definition repr-css.cpp:190
void remove(std::vector< T > &vec, T const &val)
Definition sanitize.cpp:94
int index
Glib::ustring name
Definition toolbars.cpp:55