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