Krita Source Code Documentation
Loading...
Searching...
No Matches
KoFontGlyphModel.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2024 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6#include "KoFontGlyphModel.h"
8#include <QDebug>
9#include <hb.h>
10#include <hb-ft.h>
11
13 struct InfoNode {
14 virtual ~InfoNode() {}
15 uint ucs = 0;
17 virtual int childCount() = 0;
18 };
19
20 struct GlyphInfo
21 : public InfoNode {
22
24 {
25 }
27 {
28 ucs = utf;
29 parentUcs = utf;
30 }
31 ~GlyphInfo() override {}
33 QString baseString;
34 int featureIndex = -1;
35 int childCount() override {
36 return 0;
37 }
38 };
39
41 : public InfoNode {
42 ~CodePointInfo() override {}
43
45 QString utfString = QString();
46
48 int childCount() override {
49 return glyphs.size();
50 }
51 };
53 QMap<QString, KoOpenTypeFeatureInfo> featureData;
55
56 ~Private() = default;
57
58
61
62 FT_UInt gindex;
63 FT_ULong charcode = FT_Get_First_Char(face.data(), &gindex);
64
65 while (gindex != 0) {
66 CodePointInfo cpi;
67 cpi.ucs = charcode;
68 cpi.glyphIndex = gindex;
69 cpi.utfString = QString::fromUcs4(&cpi.ucs, 1);
70 cpi.glyphs.append(GlyphInfo(cpi.ucs));
71
72 codePoints.append(cpi);
73 charcode = FT_Get_Next_Char(face.data(), charcode, &gindex);
74 }
75
76 return codePoints;
77 }
78
79 static QMap<uint, QVector<GlyphInfo>> getVSData(FT_FaceSP face) {
80 QMap<uint, QVector<GlyphInfo>> vsData;
81 hb_face_t_sp hbFace(hb_ft_face_create_referenced(face.data()));
82 hb_set_t_sp variationSelectors(hb_set_create());
83 hb_face_collect_variation_selectors(hbFace.data(), variationSelectors.data());
84
85 hb_codepoint_t hbVSPoint = HB_SET_VALUE_INVALID;
86
87 while(hb_set_next(variationSelectors.data(), &hbVSPoint)) {
88 hb_set_t_sp unicodes(hb_set_create());
89
90 hb_face_collect_variation_unicodes(hbFace.data(), hbVSPoint, unicodes.data());
91 hb_codepoint_t hbCodePointPoint = HB_SET_VALUE_INVALID;
92 while(hb_set_next(unicodes.data(), &hbCodePointPoint)) {
93 QVector<GlyphInfo> glyphs = vsData.value(hbCodePointPoint);
94 GlyphInfo gci(hbCodePointPoint);
96 gci.baseString = QString::fromUcs4(&hbVSPoint, 1);
97 glyphs.append(gci);
98 vsData.insert(hbCodePointPoint, glyphs);
99 }
100 }
101 return vsData;
102 }
103
104 static QMap<QString, KoOpenTypeFeatureInfo> getOpenTypeTables(FT_FaceSP face, QVector<CodePointInfo> &charMap, QMap<QString, KoOpenTypeFeatureInfo> previousFeatureInfo, bool gpos, bool samplesOnly, QStringList locales, QLatin1String lang = QLatin1String()) {
105 // All of this was referenced from Inkscape's OpenTypeUtil.cpp::readOpenTypeGsubTable
106 // It has since been reworked to include language testing and alternates.
107 QMap<QString, KoOpenTypeFeatureInfo> featureInfo = previousFeatureInfo;
108 hb_face_t_sp hbFace(hb_ft_face_create_referenced(face.data()));
109 hb_tag_t table = gpos? HB_OT_TAG_GPOS: HB_OT_TAG_GSUB;
110 uint targetLanguage = HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX;
111 uint targetScript = 0;
112
113 QVector<hb_language_t> localeTags;
114 Q_FOREACH(const QString locale, locales) {
115 QLatin1String l(locale.split("_").join("-").toLatin1());
116 localeTags.append(hb_language_from_string(l.data(), l.size()));
117 }
118
119 hb_language_t languageTag = lang.isEmpty()? HB_LANGUAGE_INVALID: hb_language_from_string(lang.data(), lang.size());
120 uint scriptCount = hb_ot_layout_table_get_script_tags(hbFace.data(), table, 0, nullptr, nullptr);
122 for (uint script = 0; script < scriptCount; script++) {
123 uint languageCount = hb_ot_layout_script_get_language_tags(hbFace.data(), table, script, 0, nullptr, nullptr);
124
125 for(uint j = 0; j < languageCount; j++) {
126 hb_tag_t langTag;
127 uint count = 1;
128 hb_ot_layout_script_get_language_tags(hbFace.data(), table, script, j, &count, &langTag);
129 if (count < 1) continue;
130 if (hb_ot_tag_to_language(langTag) == languageTag) {
131 targetLanguage = j;
132 targetScript = script;
133 break;
134 }
135 }
136 }
137
138 uint featureCount = hb_ot_layout_language_get_feature_tags(hbFace.data(),
139 table,
140 targetScript,
141 targetLanguage,
142 0, nullptr, nullptr);
143 hb_ot_layout_language_get_feature_tags(hbFace.data(), table, targetScript,
144 targetLanguage,
145 0, nullptr, nullptr);
146 for(uint k = 0; k < featureCount; k++) {
147 uint count = 1;
148 hb_tag_t features;
149 hb_ot_layout_language_get_feature_tags(hbFace.data(), table, targetScript,
150 targetLanguage,
151 k, &count, &features);
152 if (count < 1) continue;
153 tags.append(features);
154 }
155
156 QHash<quint32, int> glyphToCodepoint;
157 for (int i = 0; i< charMap.size(); i++) {
158 glyphToCodepoint.insert(charMap.at(i).glyphIndex, i);
159 }
160
162 for (auto it = tags.begin(); it != tags.end(); it++) {
163 char c[4];
164 hb_tag_to_string(*it, c);
165 const QLatin1String tagName(c, 4);
166 uint featureIndex;
167
168 QVector<uint> lookUpsProcessed;
169 bool found = hb_ot_layout_language_find_feature (hbFace.data(), table,
170 targetScript,
171 targetLanguage,
172 *it,
173 &featureIndex );
174
175 if (!found) {
176 continue;
177 }
178
179 hb_ot_name_id_t labelId = HB_OT_NAME_ID_INVALID;
180 hb_ot_name_id_t toolTipId = HB_OT_NAME_ID_INVALID;
181 hb_ot_name_id_t sampleId = HB_OT_NAME_ID_INVALID;
182 uint namedParameters;
183 hb_ot_name_id_t firstParamId = HB_OT_NAME_ID_INVALID;
184 KoOpenTypeFeatureInfo info = factory.infoByTag(tagName);
185 if (hb_ot_layout_feature_get_name_ids(hbFace.data(), table, featureIndex, &labelId, &toolTipId, &sampleId, &namedParameters, &firstParamId)) {
186 QVector<hb_ot_name_id_t> nameIds = {labelId, toolTipId, sampleId};
187 if (firstParamId != HB_OT_NAME_ID_INVALID) {
188 for (uint i = 0; i < namedParameters; i++) {
189 nameIds += firstParamId + i;
190 }
191 }
192
193 for(auto nameId = nameIds.begin(); nameId != nameIds.end(); nameId++) {
194 if (*nameId == HB_OT_NAME_ID_INVALID) {
195 continue;
196 }
197 QVector<hb_language_t> testLang;
198 uint length = 0;
199 if (*nameId == sampleId) {
200 testLang.append(languageTag);
201 } else {
202 testLang = localeTags;
203 }
204 testLang.append(HB_LANGUAGE_INVALID);
205 for (auto tag = testLang.begin(); tag != testLang.end(); tag++) {
206 length = hb_ot_name_get_utf8(hbFace.data(), *nameId, *tag, nullptr, nullptr);
207 if (length > 0) {
208 length+=1;
209 break;
210 }
211 }
212
213 std::vector<char> buff(length);
214 hb_ot_name_get_utf8(hbFace.data(), *nameId, languageTag, &length, buff.data());
215 if (length > 0) {
216 const QString nameString = QString::fromUtf8(buff.data(), length);
217 if (*nameId == labelId) {
218 info.name = nameString;
219 } else if (*nameId == toolTipId) {
220 info.description = nameString;
221 } else if (*nameId == sampleId) {
222 info.sample = nameString;
223 } else {
224 info.namedParameters.append(nameString);
225 }
226 }
227 }
228 }
229
230 featureInfo.insert(tagName, info);
231 if (!info.glyphPalette || (samplesOnly && !info.sample.isEmpty())) {
232 continue;
233 }
234
235 QStringList samples;
236 int lookupCount = hb_ot_layout_feature_get_lookups (hbFace.data(), table,
237 featureIndex,
238 0,
239 nullptr,
240 nullptr );
241 for (int i = 0; i < lookupCount; ++i) {
242 uint maxCount = 1;
243 uint lookUpIndex = 0;
244 hb_ot_layout_feature_get_lookups (hbFace.data(), table,
245 featureIndex,
246 i,
247 &maxCount,
248 &lookUpIndex );
249 if (maxCount < 1 || lookUpsProcessed.contains(lookUpIndex)) {
250 continue;
251 }
252
253 // https://github.com/harfbuzz/harfbuzz/issues/673 suggest against checking the lookups,
254 // but if we don't know the input glyphs, initialization can get really slow.
255 // Given this is run only when the model is created, this should be fine for now.
256
257 hb_set_t_sp glyphsBefore (hb_set_create());
258 hb_set_t_sp glyphsInput (hb_set_create());
259 hb_set_t_sp glyphsAfter (hb_set_create());
260 hb_set_t_sp glyphsOutput (hb_set_create());
261
262 hb_ot_layout_lookup_collect_glyphs (hbFace.data(), table,
263 lookUpIndex,
264 glyphsBefore.data(),
265 glyphsInput.data(),
266 glyphsAfter.data(),
267 glyphsOutput.data() );
268
269 GlyphInfo gci;
270 gci.type = OpenType;
271 gci.baseString = tagName;
272
273 hb_codepoint_t currentGlyph = HB_SET_VALUE_INVALID;
274 while(hb_set_next(glyphsInput.data(), &currentGlyph)) {
275 if (!glyphToCodepoint.contains(currentGlyph)) continue;
276 const int codePointLocation = glyphToCodepoint.value(currentGlyph);
277 CodePointInfo codePointInfo = charMap.at(codePointLocation);
278 gci.ucs = codePointInfo.ucs;
279 bool addSample = false;
280
281 uint alt_count = hb_ot_layout_lookup_get_glyph_alternates (hbFace.data(),
282 lookUpIndex, currentGlyph,
283 0,
284 nullptr, nullptr);
285
286 if (alt_count > 0) {
287 // 0 is the default value.
288 for(uint j = 1; j < alt_count; ++j) {
289 gci.featureIndex = j;
290 codePointInfo.glyphs.append(gci);
291 }
292 info.maxValue = qMax(int(alt_count), info.maxValue);
293 addSample = true;
294 } else {
295 gci.featureIndex = 1;
296 codePointInfo.glyphs.append(gci);
297 addSample = true;
298 }
299 charMap[codePointLocation] = codePointInfo;
300 if (samples.size() < 6 && addSample) {
301 samples.append(QString::fromUcs4(&gci.ucs, 1));
302 }
303 if (samples.size() >= 6 && samplesOnly) {
304 break;
305 }
306 }
307
308 lookUpsProcessed.append(lookUpIndex);
309 if (info.sample.isEmpty() && !samples.isEmpty()) {
310 info.sample = samples.join(" ");
311 }
312 featureInfo.insert(tagName, info);
313
314 }
315
316 }
317
318
319 return featureInfo;
320 }
321};
322
323
324
325
327 : QAbstractItemModel(parent)
328 , d(new Private)
329{
330}
331
336
337QString unicodeHexFromUCS(const uint codePoint) {
338 QByteArray ba;
339 ba.setNum(codePoint, 16);
340 QString hex = QString(ba);
341 return QString("U+%1").arg(hex, hex.size() > 4? 6: 4, '0');
342}
343
344QVariant KoFontGlyphModel::data(const QModelIndex &index, int role) const
345{
346 if (!index.isValid()) {
347 return QVariant();
348 }
349 if (role == Qt::DisplayRole) {
350 //qDebug() << Q_FUNC_INFO<< index << index.parent().isValid() << index.parent();
351 if (!index.parent().isValid()) {
352 return d->codePoints.value(index.row()).utfString;
353 } else {
354 Private::CodePointInfo codePoint = d->codePoints.value(index.parent().row());
355 Private::GlyphInfo glyph = codePoint.glyphs.value(index.row());
356 //qDebug () << index.parent().row() << index.row() << codePoint.utfString << codePoint.ucs;
357 if (glyph.type == UnicodeVariationSelector) {
358 return QString(codePoint.utfString + glyph.baseString);
359 } else {
360 return codePoint.utfString;
361 }
362 }
363 } else if (role == Qt::ToolTipRole) {
364 if (!index.parent().isValid()) {
365 QStringList glyphNames;
366 Private::CodePointInfo codePoint = d->codePoints.value(index.row());
367 QString base = codePoint.utfString;
368 QVector<Private::GlyphInfo> glyphList = codePoint.glyphs;
369 glyphNames.append(QString("%1 (%2)").arg(base).arg(unicodeHexFromUCS(codePoint.ucs)));
370 if (glyphList.size() > 0) {
371 glyphNames.append(i18nc("@info:tooltip", "%1 glyph variants.").arg(glyphList.size()));
372 }
373 return glyphNames.join(" ");
374 } else {
375 Private::CodePointInfo codePoint = d->codePoints.value(index.parent().row());
376 Private::GlyphInfo glyph = codePoint.glyphs.value(index.row());
377 if (glyph.type == OpenType) {
378 KoOpenTypeFeatureInfo info = d->featureData.value(glyph.baseString);
379 QString parameterString = info.namedParameters.value(glyph.featureIndex-1);
380 if (parameterString.isEmpty()) {
381 return info.name.isEmpty()? glyph.baseString: info.name;
382 } else {
383 return QString("%1: %2").arg(info.name).arg(parameterString);
384 }
385 } else {
386 return QString(codePoint.utfString + glyph.baseString);
387 }
388 }
389 } else if (role == OpenTypeFeatures) {
390 QVariantMap features;
391 if (index.parent().isValid()) {
392 Private::CodePointInfo codePoint = d->codePoints.value(index.parent().row());
393 Private::GlyphInfo glyph = codePoint.glyphs.value(index.row());
394 //qDebug () << index.parent().row() << index.row() << codePoint.utfString << codePoint.ucs;
395 if (glyph.type == OpenType) {
396 features.insert(glyph.baseString, glyph.featureIndex);
397 }
398 }
399 return features;
400 } else if (role == GlyphLabel) {
401 QString glyphId;
402 if (!index.parent().isValid()) {
403 Private::CodePointInfo codePoint = d->codePoints.value(index.row());
404 glyphId = unicodeHexFromUCS(codePoint.ucs);
405 } else {
406 Private::CodePointInfo codePoint = d->codePoints.value(index.parent().row());
407 Private::GlyphInfo glyph = codePoint.glyphs.value(index.row());
408 if (glyph.type == OpenType) {
409 glyphId = QString("'%1' %2").arg(glyph.baseString).arg(glyph.featureIndex);
410 } else if (glyph.type == UnicodeVariationSelector) {
411 glyphId = unicodeHexFromUCS(glyph.baseString.toUcs4().first());
412 } else {
413 glyphId = unicodeHexFromUCS(codePoint.ucs);
414 }
415 }
416 return glyphId;
417 } else if (role == ChildCount) {
418 int childCount = 0;
419 if (!index.parent().isValid()) {
420 Private::CodePointInfo codePoint = d->codePoints.value(index.row());
421 childCount = codePoint.childCount();
422 }
423 return childCount;
424 }
425 return QVariant();
426}
427
428QModelIndex KoFontGlyphModel::index(int row, int column, const QModelIndex &parent) const
429{
430 if (parent.isValid() && parent.row() >= 0 && parent.row() < d->codePoints.size()) {
431 Private::CodePointInfo info = d->codePoints.at(parent.row());
432 if (row >= 0 && row < info.glyphs.size()) {
433 Private::GlyphInfo glyphInfo = info.glyphs.at(row);
434 return createIndex(row, column, &glyphInfo);
435 }
436
437 } else if (row >= 0 && row < d->codePoints.size()) {
438 return createIndex(row, column);
439 }
440 return QModelIndex();
441}
442
443QModelIndex KoFontGlyphModel::parent(const QModelIndex &child) const
444{
445 if (!child.isValid() || !child.internalPointer()) {
446 return QModelIndex();
447 }
448 Private::InfoNode *node = static_cast<Private::InfoNode*>(child.internalPointer());
449 if (node) {
450 const uint targetUcs = node->ucs;
451 for(int i = 0; i < d->codePoints.size(); i++) {
452 Private::CodePointInfo info = d->codePoints.at(i);
453 if (info.ucs == targetUcs) {
454 return createIndex(i, 0);
455 }
456 }
457 }
458
459 return QModelIndex();
460}
461
462int KoFontGlyphModel::rowCount(const QModelIndex &parent) const
463{
464 if (parent.isValid()) {
465 return d->codePoints.value(parent.row()).glyphs.size();
466 }
467 return d->codePoints.size();
468}
469
470int KoFontGlyphModel::columnCount(const QModelIndex &parent) const
471{
472 Q_UNUSED(parent);
473 return 1;
474}
475
476bool KoFontGlyphModel::hasChildren(const QModelIndex &parent) const
477{
478 if (parent.isValid()) {
479 return !d->codePoints.value(parent.row()).glyphs.isEmpty();
480 }
481 return false;
482}
483
484QModelIndex KoFontGlyphModel::indexForString(QString grapheme)
485{
486 for (int i = 0; i < d->codePoints.size(); i++) {
487 if (grapheme.toUcs4().startsWith(d->codePoints.at(i).ucs)) {
488 QModelIndex idx = index(i, 0, QModelIndex());
489 return idx;
490 }
491 }
492 return QModelIndex();
493}
494
495static bool sortBlocks(const KoUnicodeBlockData &a, const KoUnicodeBlockData &b) {
496 return a.start < b.start;
497}
498
499void KoFontGlyphModel::setFace(FT_FaceSP face, QLatin1String language, bool samplesOnly)
500{
501 beginResetModel();
502 d->codePoints = Private::charMap(face);
503 d->blocks.clear();
504 QMap<uint, QVector<Private::GlyphInfo>> VSData = Private::getVSData(face);
505 KoUnicodeBlockDataFactory blockFactory;
506
507 for(auto it = d->codePoints.begin(); it != d->codePoints.end(); it++) {
508 it->glyphs.append(VSData.value(it->ucs));
509
510 auto block = d->blocks.begin();
511 for (; block != d->blocks.end(); block++) {
512 if (block->match(it->ucs)) {
513 break;
514 }
515 }
516 if (block == d->blocks.end()) {
517 KoUnicodeBlockData newBlock = blockFactory.blockForUCS(it->ucs);
518 if (newBlock != KoUnicodeBlockDataFactory::noBlock()) {
519 d->blocks.append(newBlock);
520 }
521 }
522 }
523 std::sort(d->blocks.begin(), d->blocks.end(), sortBlocks);
524 d->featureData = Private::getOpenTypeTables(face, d->codePoints, QMap<QString, KoOpenTypeFeatureInfo>() , false, samplesOnly, KLocalizedString::languages(), language);
525 d->featureData = Private::getOpenTypeTables(face, d->codePoints, d->featureData, true, samplesOnly, KLocalizedString::languages(), language);
526
527 endResetModel();
528}
529
530QHash<int, QByteArray> KoFontGlyphModel::roleNames() const
531{
532 QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
533 roles[OpenTypeFeatures] = "openType";
534 roles[GlyphLabel] = "glyphLabel";
535 roles[ChildCount] = "childCount";
536 return roles;
537}
538
540{
541 return d->blocks;
542}
543
544QMap<QString, KoOpenTypeFeatureInfo> KoFontGlyphModel::featureInfo() const
545{
546 return d->featureData;
547}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
static bool sortBlocks(const KoUnicodeBlockData &a, const KoUnicodeBlockData &b)
QString unicodeHexFromUCS(const uint codePoint)
unsigned int uint
QVector< KoUnicodeBlockData > blocks() const
blocks
bool hasChildren(const QModelIndex &parent=QModelIndex()) const override
QHash< int, QByteArray > roleNames() const override
void setFace(FT_FaceSP face, QLatin1String language=QLatin1String(), bool samplesOnly=false)
setFace set the face to retrieve glyph data for.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
int rowCount(const QModelIndex &parent=QModelIndex()) const override
QMap< QString, KoOpenTypeFeatureInfo > featureInfo() const
featureInfo
QScopedPointer< Private > d
KoFontGlyphModel(QObject *parent=nullptr)
QModelIndex parent(const QModelIndex &child) const override
int columnCount(const QModelIndex &parent=QModelIndex()) const override
QModelIndex indexForString(QString grapheme)
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
QVector< CodePointInfo > codePoints
static QMap< QString, KoOpenTypeFeatureInfo > getOpenTypeTables(FT_FaceSP face, QVector< CodePointInfo > &charMap, QMap< QString, KoOpenTypeFeatureInfo > previousFeatureInfo, bool gpos, bool samplesOnly, QStringList locales, QLatin1String lang=QLatin1String())
QMap< QString, KoOpenTypeFeatureInfo > featureData
static QVector< CodePointInfo > charMap(FT_FaceSP face)
QVector< KoUnicodeBlockData > blocks
static QMap< uint, QVector< GlyphInfo > > getVSData(FT_FaceSP face)
The KoOpenTypeFeatureInfoFactory class.
KoOpenTypeFeatureInfo infoByTag(const QLatin1String &tag) const
infoByTag
QString description
Description of the feature.
QString name
User-friendly name.
bool glyphPalette
Whether the feature should be visible in the glyph palette.
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),...
static KoUnicodeBlockData noBlock()
KoUnicodeBlockData blockForUCS(const uint &codepoint)
uint start
Start char.