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