Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
font-substitution.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Authors:
4 *
5 * Copyright (C) 2012 Authors
6 *
7 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
8 */
9
10#include <glibmm/i18n.h>
11#include <gtkmm/checkbutton.h>
12#include <gtkmm/scrolledwindow.h>
13#include <gtkmm/textview.h>
15
16#include "document.h"
17#include "inkscape.h"
18#include "object/sp-flowdiv.h"
19#include "object/sp-root.h"
20#include "object/sp-textpath.h"
21#include "object/sp-tspan.h"
22#include "preferences.h"
23#include "selection.h"
24#include "selection-chemistry.h"
25#include "text-editing.h"
26#include "ui/dialog-events.h"
27#include "ui/pack.h"
28
29namespace Inkscape::UI::Dialog {
30namespace {
31
32void show(std::vector<SPItem*> const &list, Glib::ustring const &out)
33{
34 Gtk::MessageDialog warning(_("Some fonts are not available and have been substituted."),
35 false, Gtk::MessageType::INFO, Gtk::ButtonsType::OK, true);
36 warning.set_resizable(true);
37 warning.set_title(_("Font substitution"));
38
39 sp_transientize(warning);
40
41 Gtk::TextView textview;
42 textview.set_editable(false);
43 textview.set_wrap_mode(Gtk::WrapMode::WORD);
44 textview.set_visible(true);
45 textview.get_buffer()->set_text(_(out.c_str()));
46
47 Gtk::ScrolledWindow scrollwindow;
48 scrollwindow.set_child(textview);
49 scrollwindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
50 scrollwindow.set_has_frame(true);
51 scrollwindow.set_size_request(0, 100);
52 scrollwindow.set_visible(true);
53
54 Gtk::CheckButton cbSelect;
55 cbSelect.set_label(_("Select all the affected items"));
56 cbSelect.set_active(true);
57 cbSelect.set_visible(true);
58
59 Gtk::CheckButton cbWarning;
60 cbWarning.set_label(_("Don't show this warning again"));
61 cbWarning.set_visible(true);
62
63 auto box = warning.get_content_area();
64 box->set_margin(5);
65 box->set_spacing(2);
66 UI::pack_start(*box, scrollwindow, true, true, 4);
67 UI::pack_start(*box, cbSelect, false, false);
68 UI::pack_start(*box, cbWarning, false, false);
69
71
72 if (cbWarning.get_active()) {
73 Inkscape::Preferences::get()->setBool("/options/font/substitutedlg", false);
74 }
75
76 if (cbSelect.get_active()) {
77 auto desktop = SP_ACTIVE_DESKTOP;
78 auto selection = desktop->getSelection();
79 selection->clear();
80 selection->setList(list);
81 }
82}
83
84/*
85 * Find all the fonts that are in the document but not available on the user's system
86 * and have been substituted for other fonts.
87 *
88 * Return a list of SPItems where fonts have been substituted.
89 *
90 * Walk through all the objects ...
91 * a. Build up a list of the objects with fonts defined in the style attribute
92 * b. Build up a list of the objects rendered fonts - taken for the objects layout/spans
93 * If there are fonts in a. that are not in b. then those fonts have been substituted.
94 */
95std::pair<std::vector<SPItem*>, Glib::ustring> getFontReplacedItems(SPDocument *doc)
96{
97 std::vector<SPItem*> outList;
98 std::set<Glib::ustring> setErrors;
99 std::set<Glib::ustring> setFontSpans;
100 std::map<SPItem*, Glib::ustring> mapFontStyles;
101 Glib::ustring out;
102
103 auto const allList = get_all_items(doc->getRoot(), SP_ACTIVE_DESKTOP, false, false, true);
104 for (auto item : allList) {
105 auto style = item->style;
106 Glib::ustring family = "";
107
109 // Should only need to check the first span, since the others should be covered by TSPAN's etc
110 family = te_get_layout(item)->getFontFamily(0);
111 setFontSpans.insert(family);
112 }
113 else if (auto textpath = cast<SPTextPath>(item)) {
114 if (textpath->originalPath) {
115 family = cast<SPText>(item->parent)->layout.getFontFamily(0);
116 setFontSpans.insert(family);
117 }
118 }
119 else if (is<SPTSpan>(item) || is<SPFlowtspan>(item)) {
120 // is_part_of_text_subtree (item)
121 // TSPAN layout comes from the parent->layout->_spans
122 SPObject *parent_text = item;
123 while (parent_text && !is<SPText>(parent_text)) {
124 parent_text = parent_text->parent;
125 }
126 if (parent_text) {
127 family = cast<SPText>(parent_text)->layout.getFontFamily(0);
128 // Add all the spans fonts to the set
129 for (unsigned int f = 0; f < parent_text->children.size(); f++) {
130 family = cast<SPText>(parent_text)->layout.getFontFamily(f);
131 setFontSpans.insert(family);
132 }
133 }
134 }
135
136 if (style) {
137 char const *style_font = nullptr;
138 if (style->font_family.set) {
139 style_font = style->font_family.value();
140 } else if (style->font_specification.set) {
141 style_font = style->font_specification.value();
142 } else {
143 style_font = style->font_family.value();
144 }
145
146 if (style_font) {
147 if (has_visible_text(item)) {
148 mapFontStyles.insert(std::make_pair(item, style_font));
149 }
150 }
151 }
152 }
153
154 // Check if any document styles are not in the actual layout
155 for (auto mapIter = mapFontStyles.rbegin(); mapIter != mapFontStyles.rend(); ++mapIter) {
156 SPItem *item = mapIter->first;
157 Glib::ustring fonts = mapIter->second;
158
159 // CSS font fallbacks can have more that one font listed, split the font list
160 std::vector<Glib::ustring> vFonts = Glib::Regex::split_simple("," , fonts);
161 bool fontFound = false;
162 for (auto const &font : vFonts) {
163 // trim whitespace
164 size_t startpos = font.find_first_not_of(" \n\r\t");
165 size_t endpos = font.find_last_not_of(" \n\r\t");
166 if (startpos == std::string::npos || endpos == std::string::npos) {
167 continue; // empty font name
168 }
169 auto const trimmed = font.substr(startpos, endpos - startpos + 1);
170 if (setFontSpans.find(trimmed) != setFontSpans.end() ||
171 trimmed == Glib::ustring("sans-serif") ||
172 trimmed == Glib::ustring("Sans") ||
173 trimmed == Glib::ustring("serif") ||
174 trimmed == Glib::ustring("Serif") ||
175 trimmed == Glib::ustring("monospace") ||
176 trimmed == Glib::ustring("Monospace"))
177 {
178 fontFound = true;
179 break;
180 }
181 }
182 if (!fontFound) {
183 Glib::ustring subName = getSubstituteFontName(fonts);
184 Glib::ustring err = Glib::ustring::compose(_("Font '%1' substituted with '%2'"), fonts.c_str(), subName.c_str());
185 setErrors.insert(err);
186 outList.emplace_back(item);
187 }
188 }
189
190 for (auto const &err : setErrors) {
191 out.append(err + "\n");
192 g_warning("%s", err.c_str());
193 }
194
195 return std::make_pair(std::move(outList), std::move(out));
196}
197
198} // namespace
199
201{
202 bool show_dlg = Inkscape::Preferences::get()->getBool("/options/font/substitutedlg");
203 if (!show_dlg) {
204 return;
205 }
206
207 auto [list, msg] = getFontReplacedItems(doc);
208 if (!msg.empty()) {
209 show(list, msg);
210 }
211}
212
213} // namespace Inkscape::UI::Dialog
214
215/*
216 Local Variables:
217 mode:c++
218 c-file-style:"stroustrup"
219 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
220 indent-tabs-mode:nil
221 fill-column:99
222 End:
223*/
224// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
void clear()
Unselects all selected objects.
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
Inkscape::Selection * getSelection() const
Definition desktop.h:188
Typed SVG document implementation.
Definition document.h:101
SPRoot * getRoot()
Returns our SPRoot.
Definition document.h:200
Base class for visual SVG elements.
Definition sp-item.h:109
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
SPObject * parent
Definition sp-object.h:189
ChildrenList children
Definition sp-object.h:907
Glib::ustring msg
void sp_transientize(Gtk::Window &window)
Make the argument dialog transient to the currently active document window.
Event handler for dialog windows.
std::string getSubstituteFontName(std::string const &font)
TODO: insert short description here.
SPItem * item
Dialog code.
Definition desktop.h:117
void checkFontSubstitutions(SPDocument *doc)
void pack_start(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the start of box.
Definition pack.cpp:141
int dialog_run(Gtk::Dialog &dialog)
This is a GTK4 porting aid meant to replace the removal of the Gtk::Dialog synchronous API.
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
Singleton class to access the preferences file in a convenient way.
std::vector< SPItem * > get_all_items(SPObject *from, SPDesktop *desktop, bool onlyvisible, bool onlysensitive, bool ingroups, std::vector< SPItem * > const &exclude)
TODO: insert short description here.
SPRoot: SVG <svg> implementation.
TODO: insert short description here.
TODO: insert short description here.
SPDesktop * desktop
Text::Layout const * te_get_layout(SPItem const *item)
bool has_visible_text(SPObject const *obj)
bool is_top_level_text_object(SPObject const *obj)