Krita Source Code Documentation
Loading...
Searching...
No Matches
OpenTypeFeatureModel.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2025 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6#include <QList>
7
8#include <KoFontGlyphModel.h>
9#include <KoCSSFontInfo.h>
12#include <KoSvgTextProperties.h>
13#include <KoFontRegistry.h>
16
17
47
49 : QAbstractItemModel(parent)
50 , d(new Private(this))
51{
52}
53
57
58QModelIndex OpenTypeFeatureModel::index(int row, int column, const QModelIndex &parent) const
59{
60 Q_UNUSED(parent)
61 if (column != 0) return QModelIndex();
62 if (row >= 0 && row < d->currentFeatures.size()) return createIndex(row, column, &row);
63 return QModelIndex();
64}
65
66QModelIndex OpenTypeFeatureModel::parent(const QModelIndex &index) const
67{
68 Q_UNUSED(index)
69 return QModelIndex();
70}
71
72int OpenTypeFeatureModel::rowCount(const QModelIndex &parent) const
73{
74 Q_UNUSED(parent)
75 return d->currentFeatures.size();
76}
77
78int OpenTypeFeatureModel::columnCount(const QModelIndex &parent) const
79{
80 Q_UNUSED(parent)
81 return 1;
82}
83
84QVariant OpenTypeFeatureModel::data(const QModelIndex &index, int role) const
85{
86 if (!index.isValid())
87 return QVariant();
88
89 const QString feature = d->currentFeatures.keys().at(index.row());
90 KoOpenTypeFeatureInfo info = d->featureByTag(QLatin1String(feature.toLatin1()));
91 if (role == Qt::DisplayRole) {
92 return info.name;
93 } else if (role == Qt::ToolTipRole) {
94 return info.description;
95 } else if (role == Qt::EditRole) {
96 return d->currentFeatures.value(feature, QVariant(0)).toInt();
97 } else if (role == Tag) {
98 return feature;
99 } else if (role == Sample) {
100 return info.sample;
101 } else if (role == Parameters) {
102 QVariantList features;
103 for (int i = 0; i <= info.maxValue; i++) {
104 QVariantMap map;
105 map.insert("value", QVariant(i));
106 QString entry = i > 0? info.namedParameters.value(i-1): QString();
107 if (entry.isEmpty()) {
108 if (i == 1 && info.maxValue == 1) {
109 entry = info.name + ": " + i18nc("Feature value toggle", "On");
110 } else if (i == 0) {
111 entry = info.name + ": " + i18nc("Feature value toggle", "Off");
112 } else {
113 entry = info.name + ": " + QString::number(i);
114 }
115 }
116 map.insert("display", entry);
117 features.append(map);
118 }
119 return features;
120 } else if (role == Max) {
121 return info.maxValue;
122 }
123 return QVariant();
124}
125
126bool OpenTypeFeatureModel::setData(const QModelIndex &index, const QVariant &value, int role)
127{
128 if (data(index, role) != value) {
129 // FIXME: Implement me!
130
131 if (index.isValid() && role == Qt::EditRole) {
132 const QString feature = d->currentFeatures.keys().at(index.row());
133 d->currentFeatures.insert(feature, value.toInt());
135 emit dataChanged(index, index, {role});
136 return true;
137 }
138 return false;
139 }
140
141 return false;
142}
143
144Qt::ItemFlags OpenTypeFeatureModel::flags(const QModelIndex &index) const
145{
146 Qt::ItemFlags flags = QAbstractItemModel::flags(index) | Qt::ItemNeverHasChildren | Qt::ItemIsEditable;
147
148 return flags;
149}
150
151QHash<int, QByteArray> OpenTypeFeatureModel::roleNames() const
152{
153 QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
154 roles[Tag] = "tag";
155 roles[Sample] = "sample";
156 roles[Parameters] = "parameters";
157 roles[Max] = "max";
158 return roles;
159}
160
162{
163 const bool changeFont = props.cssFontInfo() != d->fontInfo;
164 const QVariantMap newFeatures = props.propertyOrDefault(KoSvgTextProperties::FontFeatureSettingsId).toMap();
165 const bool changeFeatures = d->currentFeatures != newFeatures;
166
167 if (!changeFont && !changeFeatures) return;
168
169 beginResetModel();
170 if (changeFont) {
171 const qreal res = 72.0;
172 QVector<int> lengths;
173 const KoCSSFontInfo info = props.cssFontInfo();
174 const std::vector<FT_FaceSP> faces = KoFontRegistry::instance()->facesForCSSValues(
175 lengths,
176 info,
177 QString(),
178 static_cast<quint32>(res),
179 static_cast<quint32>(res));
180
181 if (!faces.empty()) {
182 QString language = props.propertyOrDefault(KoSvgTextProperties::TextLanguage).toString();
183 // NOTE: We're retrieving only enough data for 6 samples here, to speed up loading.
184 d->glyphModel->setFace(faces.front(), QLatin1String(language.toLatin1()), true);
185 d->allFeatures->setAvailableFeatures(d->availableFeatures());
186 d->fontInfo = props.cssFontInfo();
187 }
188 }
189
190 if (changeFeatures) {
191 d->currentFeatures = newFeatures;
193 }
194 endResetModel();
195}
196
198{
199 return d->currentFeatures;
200}
201
202void OpenTypeFeatureModel::setOpenTypeFeatures(const QVariantMap &newOpenTypeFeatures)
203{
204 if (d->currentFeatures == newOpenTypeFeatures)
205 return;
206 beginResetModel();
207 d->currentFeatures = newOpenTypeFeatures;
208 endResetModel();
210}
211
212void OpenTypeFeatureModel::addFeature(const QString &tag)
213{
214 if (tag.isEmpty()) return;
215 if (d->currentFeatures.keys().contains(tag)) return;
216
217 QVariantMap dummy = d->currentFeatures;
218 dummy.insert(tag, QVariant(1));
219 const int index = dummy.keys().indexOf(tag);
220
221 beginInsertRows(QModelIndex(), index, index);
222 d->currentFeatures.insert(tag, QVariant(1));
223 endInsertRows();
225}
226
228{
229 if (tag.isEmpty()) return;
230 const int index = d->currentFeatures.keys().indexOf(tag);
231 if (index < 0) return;
232
233 beginRemoveRows(QModelIndex(), index, index);
234 d->currentFeatures.remove(tag);
235 endRemoveRows();
237}
238
239QAbstractItemModel *OpenTypeFeatureModel::allFeatureModel() const
240{
241 return d->allFeatures;
242}
243
245{
247 if (textPropertiesModel) {
248 KoSvgTextPropertyData data = textPropertiesModel->textData.get();
249 main = data.commonProperties;
250 main.inheritFrom(data.inheritedProperties);
251 }
253}
254
255/********* AllOpenTypeFeaturesModel ************/
257
262
263 bool featureAvailable(const QString &tag) const {
264 return availableTags.contains(tag);
265 }
266
267 KoOpenTypeFeatureInfo featureByTag(QLatin1String tag) const {
268 Q_FOREACH(KoOpenTypeFeatureInfo feature, availableFeatures) {
269 if (feature.tag == tag) {
270 return feature;
271 break;
272 }
273 }
274
275 return factory.infoByTag(tag);
276 }
277
278 // OpenType allows for "custom" opentype features, this forloop checks for those.
280 QStringList tags;
281 allTags = factory.tags();
282 Q_FOREACH(KoOpenTypeFeatureInfo feature, availableFeatures) {
283 const QString tag = QString::fromLatin1(feature.tag.data(), 4);
284 if (!allTags.contains(tag)) {
285 allTags.append(tag);
286 }
287 tags.append(tag);
288 }
289 availableTags = tags;
290 }
291};
292
294 : QAbstractListModel(parent)
295 , d(new Private)
296{
297
298}
299
304
306{
307 beginResetModel();
308 d->availableFeatures = features;
309 d->setAvailableTags();
310 endResetModel();
311}
312
313QVariant AllOpenTypeFeaturesModel::data(const QModelIndex &index, int role) const
314{
315 if (!index.isValid())
316 return QVariant();
317
318 const QString feature = d->allTags.at(index.row());
319 KoOpenTypeFeatureInfo info = d->featureByTag(QLatin1String(feature.toLatin1(), 4));
320 if (role == Qt::DisplayRole) {
321 return info.name;
322 } else if (role == Qt::ToolTipRole) {
323 return info.description;
324 } else if (role == Tag) {
325 return feature;
326 } else if (role == Sample) {
327 return info.sample;
328 } else if (role == Available) {
329 return d->featureAvailable(feature);
330 }
331 return QVariant();
332}
333
334int AllOpenTypeFeaturesModel::rowCount(const QModelIndex &parent) const
335{
336 Q_UNUSED(parent)
337 return d->allTags.size();
338}
339
340QVariant AllOpenTypeFeaturesModel::headerData(int section, Qt::Orientation orientation, int role) const
341{
342 if (role == Qt::DisplayRole) {
343 if (orientation == Qt::Horizontal && section == 0) {
344 return i18nc("@title:column", "OpenType Feature Tag");
345 }
346 }
347 return QVariant();
348}
349
350QHash<int, QByteArray> AllOpenTypeFeaturesModel::roleNames() const
351{
352 QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
353 roles[Tag] = "tag";
354 roles[Sample] = "sample";
355 roles[Available] = "available";
356 return roles;
357}
358
360 : QSortFilterProxyModel(parent)
361{
362
363}
364
366{
367 const QModelIndex idx = index(0, 0, QModelIndex());
368 if (!idx.isValid()) return QString();
369 return data(idx, AllOpenTypeFeaturesModel::Tag).toString();
370}
371
372#include <QDebug>
373bool OpenTypeFeatureFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
374{
375 const QModelIndex idx = sourceModel()->index(source_row, 0, source_parent);
376 if (!idx.isValid()) return false;
377 const bool available = sourceModel()->data(idx, AllOpenTypeFeaturesModel::Available).toBool();
378 if (m_filterAvailable && !available) return false;
379 if (filterRegularExpression().pattern().isEmpty()) return true;
380 const QString tag = sourceModel()->data(idx, AllOpenTypeFeaturesModel::Tag).toString();
381 const QString name = sourceModel()->data(idx, Qt::DisplayRole).toString();
382 return (tag.contains(filterRegularExpression()) || name.contains(filterRegularExpression()));
383}
384
389
391{
392 if (m_filterAvailable == newFilterAvailable)
393 return;
394 m_filterAvailable = newFilterAvailable;
395 invalidateFilter();
397}
398
400{
401 return filterRegularExpression().pattern();
402}
403
404void OpenTypeFeatureFilterModel::setSearchText(const QString &newSearchText)
405{
406 if (filterRegularExpression().pattern() == newSearchText)
407 return;
408 setFilterRegularExpression(newSearchText);
409 emit searchTextChanged();
410}
float value(const T *src, size_t ch)
QVariant data(const QModelIndex &index, int role) const override
void setAvailableFeatures(const QList< KoOpenTypeFeatureInfo > &features)
QVariant headerData(int section, Qt::Orientation orientation, int role) const
const QScopedPointer< Private > d
AllOpenTypeFeaturesModel(QObject *parent=nullptr)
int rowCount(const QModelIndex &parent) const override
QHash< int, QByteArray > roleNames() const override
The KoFontGlyphModel class Creates a tree model of all the glyphs in a given face.
QMap< QString, KoOpenTypeFeatureInfo > featureInfo() const
featureInfo
std::vector< FT_FaceSP > facesForCSSValues(QVector< int > &lengths, KoCSSFontInfo info=KoCSSFontInfo(), const QString &text="", quint32 xRes=72, quint32 yRes=72, bool disableFontMatching=false, const QString &language=QString())
facesForCSSValues This selects a font with fontconfig using the given values. If "text" is not empty ...
static KoFontRegistry * instance()
The KoSvgTextPropertiesModel class.
lager::cursor< KoSvgTextPropertyData > textData
@ FontFeatureSettingsId
QStringList.
@ TextLanguage
a language string.
KoCSSFontInfo cssFontInfo() const
cssFontInfo
QVariant propertyOrDefault(PropertyId id) const
void setFilterAvailable(bool newFilterAvailable)
void setSearchText(const QString &newSearchText)
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
Q_INVOKABLE QString firstValidTag() const
OpenTypeFeatureFilterModel(QObject *parent=nullptr)
int rowCount(const QModelIndex &parent=QModelIndex()) const override
void setFromTextProperties(const KoSvgTextProperties &props)
Q_INVOKABLE void setFromTextPropertiesModel(KoSvgTextPropertiesModel *textPropertiesModel)
setFromTextPropertiesModel Set the current glyph model font from the lager text properties model....
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
int columnCount(const QModelIndex &parent=QModelIndex()) const override
Q_INVOKABLE QAbstractItemModel * allFeatureModel() const
featureFilterModel
QModelIndex parent(const QModelIndex &index) const override
OpenTypeFeatureModel(QObject *parent=nullptr)
void setOpenTypeFeatures(const QVariantMap &newOpenTypeFeatures)
@ Max
int, max count, default is 1, but can be larger depending on the font.
@ Sample
QString, the sample for this feature, may be empty.
@ Tag
QString, opentype tag.
@ Parameters
QVariantList, indices with names of the feature count.
Q_INVOKABLE void removeFeature(const QString &tag)
Q_INVOKABLE void addFeature(const QString &tag)
const QScopedPointer< Private > d
QHash< int, QByteArray > roleNames() const override
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
Qt::ItemFlags flags(const QModelIndex &index) const override
void openTypeFeaturesChanged()
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
int main(int argc, char **argv)
Definition main.cpp:26
KoOpenTypeFeatureInfo featureByTag(QLatin1String tag) const
KoOpenTypeFeatureInfoFactory factory
QList< KoOpenTypeFeatureInfo > availableFeatures
bool featureAvailable(const QString &tag) const
The KoCSSFontInfo class Convenience struct to make it easier to use KoFontRegistry....
The KoOpenTypeFeatureInfoFactory class.
KoOpenTypeFeatureInfo infoByTag(const QLatin1String &tag) const
infoByTag
QString description
Description of the feature.
QString name
User-friendly name.
QString sample
Sample of the feature, if any. Only used by CVXX features and retrieved from the font.
int maxValue
The maximum value possible, this is by default 1 (on), but for alternate substitution(gsub 3),...
The KoSvgTextPropertyData struct.
AllOpenTypeFeaturesModel * allFeatures
KoOpenTypeFeatureInfoFactory factory
KoOpenTypeFeatureInfo featureByTag(QLatin1String tag) const
QList< KoOpenTypeFeatureInfo > availableFeatures() const