Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2026 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 : #include "fontfacext.h"
23 :
24 : #include <algorithm>
25 : #include <array>
26 : #include <cctype>
27 : #include <string_view>
28 :
29 : #include <lodepng.h>
30 :
31 : #include "global/serialization/zipreader.h"
32 : #include "global/serialization/msgpack.h"
33 : #include "global/stringutils.h"
34 : #include "global/io/buffer.h"
35 : #include "global/io/fileinfo.h"
36 : #include "global/containers.h"
37 : #include "log.h"
38 :
39 : using namespace muse::draw;
40 :
41 2052 : static bool isNumber(const std::string& s)
42 : {
43 2052 : return !s.empty()
44 8460 : && (std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isdigit(c); })
45 0 : || (s[0] == '-' && s.size() > 1 && std::all_of(s.begin() + 1, s.end(), [](unsigned char c) {
46 0 : return std::isdigit(c);
47 2052 : })));
48 : }
49 :
50 208 : bool FontFaceXT::isFtx(const ByteArray& data)
51 : {
52 : static constexpr std::array<uint8_t, 3> MARKER = { 'f', 't', 'x' };
53 208 : return ZipReader::startsWithEntry(data, MARKER);
54 : }
55 :
56 6 : FontFaceXT::FontFaceXT()
57 : {
58 6 : }
59 :
60 6 : FontFaceXT::~FontFaceXT()
61 : {
62 6 : }
63 :
64 6 : bool FontFaceXT::load(const FaceKey& key, const FontData& fontData, bool isSymbolMode)
65 : {
66 6 : m_key = key;
67 6 : m_isSymbolMode = isSymbolMode;
68 :
69 6 : if (!fontData.valid()) {
70 0 : LOGE() << "empty font data: " << key.dataKey.family().id();
71 0 : return false;
72 : }
73 :
74 6 : m_fileBuffer = std::make_unique<muse::io::Buffer>(muse::ByteArray(fontData.data));
75 6 : m_fileBuffer->open(muse::io::IODevice::ReadOnly);
76 :
77 6 : m_zip = std::make_unique<muse::ZipReader>(m_fileBuffer.get());
78 :
79 : // meta
80 : {
81 12 : muse::ByteArray metaData = m_zip->fileData("meta.txt");
82 6 : if (metaData.empty()) {
83 0 : LOGE() << "meta is empty";
84 0 : return false;
85 : }
86 :
87 6 : std::string meta(metaData.constChar(), metaData.size());
88 12 : std::vector<std::string> metas;
89 18 : muse::strings::split(meta, metas, "\n");
90 :
91 6 : std::string ver;
92 6 : std::string glyphs;
93 60 : for (const std::string& p : metas) {
94 54 : size_t i = p.find(':');
95 54 : if (i == std::string::npos) {
96 0 : LOGE() << "failed parse param: " << p;
97 0 : continue;
98 : }
99 :
100 54 : std::string name = p.substr(0, i);
101 54 : std::string valStr = p.substr(i + 1);
102 :
103 54 : if (name == "version") {
104 6 : ver = valStr;
105 48 : } else if (name == "glyphs") {
106 6 : glyphs = valStr;
107 42 : } else if (name == "leading") {
108 6 : m_leading = std::stol(valStr);
109 36 : } else if (name == "ascent") {
110 6 : m_ascent = std::stol(valStr);
111 30 : } else if (name == "descent") {
112 6 : m_descent = std::stol(valStr);
113 24 : } else if (name == "xHeight") {
114 6 : m_xHeight = std::stol(valStr);
115 18 : } else if (name == "capHeight") {
116 6 : m_capHeight = std::stol(valStr);
117 12 : } else if (name == "underlinePos") {
118 6 : m_underlinePos = std::stol(valStr);
119 6 : } else if (name == "lineWidth") {
120 6 : m_lineWidth = std::stol(valStr);
121 : } else {
122 0 : LOGW() << "unknown param: " << name;
123 : }
124 54 : }
125 :
126 6 : LOGI() << "fxt version: " << ver << ", glyphs: " << glyphs << ", family: " << key.dataKey.family().id();
127 6 : }
128 :
129 : // ligatures
130 12 : muse::ByteArray ligaturesData = m_zip->fileData("ligatures.txt");
131 6 : if (!ligaturesData.empty()) {
132 6 : std::string ligaturesStr(ligaturesData.constChar(), ligaturesData.size());
133 6 : std::vector<std::string> ligatureStrs;
134 18 : muse::strings::split(ligaturesStr, ligatureStrs, "\n");
135 :
136 42 : for (const std::string& str : ligatureStrs) {
137 36 : if (str.empty()) {
138 6 : continue;
139 : }
140 :
141 30 : size_t sepIdx = str.find('=');
142 30 : if (sepIdx == std::string::npos) {
143 0 : continue;
144 : }
145 :
146 30 : std::string valStr = str.substr(0, sepIdx);
147 30 : std::string keyStr = str.substr(sepIdx + 1);
148 30 : std::vector<std::string> keyStrs;
149 90 : muse::strings::split(keyStr, keyStrs, " ");
150 :
151 30 : Ligature l;
152 30 : l.first = std::stoi(valStr);
153 132 : for (const std::string& k : keyStrs) {
154 102 : if (k.empty()) {
155 30 : continue;
156 : }
157 72 : l.second.push_back(std::stoi(k));
158 : }
159 :
160 30 : m_ligatures.push_back(l);
161 30 : }
162 :
163 6 : std::sort(m_ligatures.begin(), m_ligatures.end(), [](const Ligature& l1, const Ligature& l2) {
164 60 : return l1.second.size() > l2.second.size();
165 : });
166 6 : }
167 :
168 : // kernings
169 12 : muse::ByteArray kerningsData = m_zip->fileData("kernings.txt");
170 6 : if (!kerningsData.empty()) {
171 6 : std::string kerningsStr(kerningsData.constChar(), kerningsData.size());
172 6 : std::vector<std::string> kerningStrs;
173 18 : muse::strings::split(kerningsStr, kerningStrs, "\n");
174 :
175 76158 : for (const std::string& str : kerningStrs) {
176 76152 : if (str.empty()) {
177 6 : continue;
178 : }
179 :
180 76146 : size_t sepIdx = str.find('=');
181 76146 : if (sepIdx == std::string::npos) {
182 0 : continue;
183 : }
184 :
185 76146 : std::string keyStr = str.substr(0, sepIdx);
186 76146 : std::string valStr = str.substr(sepIdx + 1);
187 76146 : std::vector<std::string> keyStrs;
188 228438 : muse::strings::split(keyStr, keyStrs, " ");
189 76146 : if (keyStrs.size() != 2) {
190 : //error in file
191 0 : continue;
192 : }
193 :
194 76146 : f26dot6_t advance = std::stoi(valStr);
195 76146 : std::pair<glyph_idx_t, glyph_idx_t> keyPair;
196 76146 : keyPair.first = std::stoi(keyStrs[0]);
197 76146 : keyPair.second = std::stoi(keyStrs[1]);
198 :
199 76146 : m_kernings.insert({ keyPair, advance });
200 76146 : }
201 6 : }
202 :
203 : {
204 : // glyphIndecies
205 12 : muse::ByteArray glyphIndeciesData = m_zip->fileData("glyphIndecies.txt");
206 6 : if (!glyphIndeciesData.empty()) {
207 6 : std::string glyphIndeciesStr(glyphIndeciesData.constChar(), glyphIndeciesData.size());
208 6 : std::vector<std::string> glyphIndexStrs;
209 18 : muse::strings::split(glyphIndeciesStr, glyphIndexStrs, "\n");
210 :
211 2064 : for (const std::string& str : glyphIndexStrs) {
212 2058 : if (str.empty()) {
213 6 : continue;
214 : }
215 :
216 2052 : std::vector<std::string> glyphIndexPair;
217 6156 : muse::strings::split(str, glyphIndexPair, " ");
218 :
219 2052 : if (glyphIndexPair.size() != 2) {
220 0 : continue;
221 : }
222 :
223 2052 : glyph_idx_t glyphIdx = static_cast<glyph_idx_t>(std::stoi(glyphIndexPair[1]));
224 :
225 2052 : if (isNumber(glyphIndexPair[0])) {
226 2052 : m_unicodeGlyphs.insert({ static_cast<char32_t>(std::stoi(glyphIndexPair[0])), glyphIdx });
227 : } else {
228 0 : m_nonUnicodeGlyphs.insert({ glyphIndexPair[0], glyphIdx });
229 : }
230 2052 : }
231 6 : }
232 6 : }
233 :
234 : {
235 : // atlas texture
236 12 : muse::ByteArray metaData = m_zip->fileData("atlatInfo.txt");
237 6 : if (metaData.empty()) {
238 0 : return true;
239 : }
240 6 : m_atlastexture.loadAtlasMetaInfo(metaData);
241 :
242 12 : muse::ByteArray atlasPng = m_zip->fileData("atlas.png");
243 6 : if (atlasPng.empty()) {
244 0 : LOGE() << "atlas.png is empty";
245 0 : return false;
246 : }
247 6 : if (!m_atlastexture.loadAtlas(atlasPng)) {
248 0 : return false;
249 : }
250 6 : }
251 :
252 6 : return true;
253 6 : }
254 :
255 1 : const FaceKey& FontFaceXT::key() const
256 : {
257 1 : return m_key;
258 : }
259 :
260 1 : bool FontFaceXT::isSymbolMode() const
261 : {
262 1 : return m_isSymbolMode;
263 : }
264 :
265 1 : f26dot6_t FontFaceXT::leading() const
266 : {
267 1 : return m_leading;
268 : }
269 :
270 1 : f26dot6_t FontFaceXT::ascent() const
271 : {
272 1 : return m_ascent;
273 : }
274 :
275 1 : f26dot6_t FontFaceXT::descent() const
276 : {
277 1 : return m_descent;
278 : }
279 :
280 1 : f26dot6_t FontFaceXT::xHeight() const
281 : {
282 1 : return m_xHeight;
283 : }
284 :
285 2 : f26dot6_t FontFaceXT::capHeight() const
286 : {
287 2 : return m_capHeight;
288 : }
289 :
290 1 : f26dot6_t FontFaceXT::underlinePos() const
291 : {
292 1 : return m_underlinePos;
293 : }
294 :
295 1 : f26dot6_t FontFaceXT::lineWidth() const
296 : {
297 1 : return m_lineWidth;
298 : }
299 :
300 18 : void FontFaceXT::applyLigatures(std::vector<ShapedGlyph>& glyphs, const Ligatures& ls)
301 : {
302 108 : for (const Ligature& l : ls) {
303 90 : if (l.second.empty() || l.second.size() > glyphs.size() || (l.second.size() == 1 && l.second.front() == l.first)) {
304 6 : continue;
305 : }
306 :
307 84 : auto from = glyphs.begin();
308 : while (1) {
309 84 : auto it = std::search(from, glyphs.end(), l.second.begin(), l.second.end(),
310 663 : [](const ShapedGlyph& glyph, glyph_idx_t idx) { return glyph.idx == idx; });
311 84 : if (it != glyphs.end()) {
312 0 : it->idx = l.first;
313 0 : for (size_t i = 1; i < l.second.size(); ++i) {
314 0 : ++it;
315 0 : it->idx = 0;
316 : }
317 0 : from = std::next(it);
318 : } else {
319 84 : break;
320 : }
321 0 : }
322 : }
323 18 : }
324 :
325 18 : std::vector<ShapedGlyph> FontFaceXT::glyphs(const char32_t* text, int text_length) const
326 : {
327 18 : if (text_length < 1) {
328 0 : return std::vector<ShapedGlyph>();
329 : }
330 :
331 18 : std::vector<ShapedGlyph> result;
332 :
333 18 : std::vector<char32_t> data(text, text + text_length);
334 :
335 18 : result.reserve(text_length);
336 :
337 153 : for (size_t i = 0; i < data.size(); ++i) {
338 135 : const char32_t ch = data[i];
339 135 : if (ch == 0) {
340 0 : continue;
341 : }
342 :
343 135 : glyph_idx_t idx = glyphIndex(ch);
344 135 : if (idx == 0) {
345 0 : LOGE() << "font: " << m_key.dataKey.family().id() << " not found glyph: " << (int)ch;
346 0 : continue;
347 : }
348 :
349 135 : result.push_back({ idx, 0, static_cast<uint32_t>(i) });
350 : }
351 :
352 18 : if (!m_isSymbolMode) {
353 18 : applyLigatures(result, m_ligatures);
354 : }
355 :
356 153 : muse::remove_if(result, [](const ShapedGlyph& glyph) { return glyph.idx == 0; });
357 :
358 153 : for (auto it = result.begin(); it != result.end(); ++it) {
359 135 : if (auto nextIt = std::next(it); nextIt != result.end()) {
360 117 : it->x_advance = glyphAdvance(it->idx, nextIt->idx);
361 : } else {
362 18 : it->x_advance = glyphAdvance(it->idx);
363 : }
364 : }
365 :
366 18 : return result;
367 18 : }
368 :
369 172 : glyph_idx_t FontFaceXT::glyphIndex(char32_t ucs4) const
370 : {
371 172 : return muse::value(m_unicodeGlyphs, ucs4);
372 : }
373 :
374 0 : glyph_idx_t FontFaceXT::glyphIndex(const std::string& glyphName) const
375 : {
376 0 : return muse::value(m_nonUnicodeGlyphs, glyphName);
377 : }
378 :
379 19 : char32_t FontFaceXT::findCharCode(glyph_idx_t idx) const
380 : {
381 19 : return muse::key(m_unicodeGlyphs, idx);
382 : }
383 :
384 64 : FBBox FontFaceXT::glyphBbox(glyph_idx_t idx) const
385 : {
386 64 : return m_isSymbolMode ? glyphData(idx).symBbox : glyphData(idx).textBbox;
387 : }
388 :
389 109 : f26dot6_t FontFaceXT::glyphAdvance(glyph_idx_t idx) const
390 : {
391 109 : return m_isSymbolMode ? glyphData(idx).symAdvance : glyphData(idx).textAdvance;
392 : }
393 :
394 18 : const GlyphImage& FontFaceXT::glyphImage(glyph_idx_t idx) const
395 : {
396 18 : return glyphData(idx).glyphImage;
397 : }
398 :
399 191 : const FontFaceXT::GlyphData& FontFaceXT::glyphData(glyph_idx_t idx) const
400 : {
401 191 : auto it = m_cache.find(idx);
402 191 : if (it != m_cache.end()) {
403 102 : return it->second;
404 : }
405 :
406 89 : muse::ByteArray data = m_zip->fileData(std::to_string(idx));
407 89 : muse::io::Buffer buf(&data);
408 89 : buf.open(muse::io::IODevice::ReadOnly);
409 :
410 89 : std::pair<glyph_idx_t, GlyphData> v;
411 89 : v.first = idx;
412 89 : v.second.read(&buf);
413 89 : v.second.glyphImage.sdf = m_atlastexture.getGlyphImage(idx);
414 :
415 89 : return m_cache.insert(std::move(v)).first->second;
416 89 : }
417 :
418 117 : f26dot6_t FontFaceXT::glyphAdvance(glyph_idx_t idx, glyph_idx_t nextIdx) const
419 : {
420 117 : auto kerningPair = std::make_pair(idx, nextIdx);
421 :
422 117 : if (auto it = m_kernings.find(kerningPair); it != m_kernings.end()) {
423 45 : return it->second;
424 : }
425 :
426 72 : return glyphAdvance(idx);
427 : }
428 :
429 0 : void FontFaceXT::GlyphData::write(muse::io::IODevice* d) const
430 : {
431 0 : muse::ByteArray data = muse::msgpack::pack(static_cast<int64_t>(textBbox.x()),
432 0 : static_cast<int64_t>(textBbox.y()),
433 0 : static_cast<int64_t>(textBbox.width()),
434 0 : static_cast<int64_t>(textBbox.height()),
435 0 : static_cast<int64_t>(textAdvance),
436 0 : static_cast<int64_t>(symBbox.x()),
437 0 : static_cast<int64_t>(symBbox.y()),
438 0 : static_cast<int64_t>(symBbox.width()),
439 0 : static_cast<int64_t>(symBbox.height()),
440 0 : static_cast<int64_t>(symAdvance),
441 0 : glyphImage.rect.x(), glyphImage.rect.y(),
442 0 : glyphImage.rect.width(), glyphImage.rect.height(),
443 0 : glyphImage.range);
444 0 : d->write(data);
445 0 : }
446 :
447 89 : void FontFaceXT::GlyphData::read(muse::io::IODevice* d)
448 : {
449 89 : int64_t textBboxX = 0;
450 89 : int64_t textBboxY = 0;
451 89 : int64_t textBboxW = 0;
452 89 : int64_t textBboxH = 0;
453 89 : int64_t textAdvanceValue = 0;
454 89 : int64_t symBboxX = 0;
455 89 : int64_t symBboxY = 0;
456 89 : int64_t symBboxW = 0;
457 89 : int64_t symBboxH = 0;
458 89 : int64_t symAdvanceValue = 0;
459 89 : double rectX = 0.0;
460 89 : double rectY = 0.0;
461 89 : double rectW = 0.0;
462 89 : double rectH = 0.0;
463 89 : float range = 0.f;
464 :
465 89 : const bool ok = muse::msgpack::unpack(d->readAll(), textBboxX, textBboxY, textBboxW, textBboxH, textAdvanceValue,
466 : symBboxX, symBboxY, symBboxW, symBboxH, symAdvanceValue,
467 : rectX, rectY, rectW, rectH, range);
468 89 : if (!ok) {
469 0 : LOGE() << "failed read glyph data";
470 0 : return;
471 : }
472 :
473 89 : textBbox = FBBox(textBboxX, textBboxY, textBboxW, textBboxH);
474 89 : textAdvance = textAdvanceValue;
475 89 : symBbox = FBBox(symBboxX, symBboxY, symBboxW, symBboxH);
476 89 : symAdvance = symAdvanceValue;
477 89 : glyphImage.rect = muse::RectF(rectX, rectY, rectW, rectH);
478 89 : glyphImage.range = range;
479 : }
480 :
481 6 : bool AtlasTexture::loadAtlas(const muse::ByteArray& atlas)
482 : {
483 6 : const unsigned error = lodepng::decode(m_atlas, m_atlasWidth, m_atlasHeight,
484 : atlas.constData(), atlas.size(), LCT_GREY);
485 6 : if (error != 0) {
486 0 : LOGE() << "failed to decode atlas: " << lodepng_error_text(error);
487 0 : m_atlas.clear();
488 0 : m_atlasWidth = 0;
489 0 : m_atlasHeight = 0;
490 0 : return false;
491 : }
492 :
493 6 : return true;
494 : }
495 :
496 6 : void AtlasTexture::loadAtlasMetaInfo(const muse::ByteArray& metaData)
497 : {
498 6 : std::string meta(metaData.constChar(), metaData.size());
499 6 : std::vector<std::string> glyphsInfos;
500 18 : muse::strings::split(meta, glyphsInfos, "\n");
501 :
502 2046 : for (const std::string& info : glyphsInfos) {
503 2040 : if (info.empty()) {
504 0 : continue;
505 : }
506 2040 : GlyphInfo glyphInfo;
507 2040 : size_t i = info.find('=');
508 2040 : if (i == std::string::npos) {
509 0 : LOGE() << "failed parse param: " << info;
510 0 : continue;
511 : }
512 2040 : glyphInfo.gidx = std::stoi(info.substr(0, i));
513 :
514 2040 : std::string valStr = info.substr(i + 1);
515 2040 : std::vector<std::string> infos;
516 6120 : muse::strings::split(valStr, infos, ":");
517 2040 : if (infos.size() != 5) {
518 0 : continue;
519 : }
520 :
521 2040 : glyphInfo.rect.setX(std::stoi(infos[0]));
522 2040 : glyphInfo.rect.setY(std::stoi(infos[1]));
523 2040 : glyphInfo.rect.setWidth(std::stoi(infos[2]));
524 2040 : glyphInfo.rect.setHeight(std::stoi(infos[3]));
525 2040 : glyphInfo.rotated = std::stoi(infos[4]);
526 2040 : m_glyphInfos.insert({ glyphInfo.gidx, glyphInfo });
527 2040 : }
528 6 : }
529 :
530 89 : Sdf AtlasTexture::getGlyphImage(glyph_idx_t idx)
531 : {
532 89 : Sdf result;
533 89 : auto it = m_glyphInfos.find(idx);
534 89 : if (it == m_glyphInfos.end()) {
535 4 : return result;
536 : }
537 :
538 85 : auto width = it->second.rect.width();
539 85 : auto height = it->second.rect.height();
540 85 : auto posX = it->second.rect.x();
541 85 : auto posY = it->second.rect.y();
542 :
543 85 : if (width <= 0 || height <= 0 || posX < 0 || posY < 0
544 85 : || posX + width > static_cast<int>(m_atlasWidth)
545 85 : || posY + height > static_cast<int>(m_atlasHeight)) {
546 0 : LOGE() << "invalid atlas glyph rect: " << idx;
547 0 : return result;
548 : }
549 :
550 85 : result.bitmap.reserve(width * height);
551 :
552 85 : if (it->second.rotated) {
553 2174 : for (int x = 0; x < width; ++x) {
554 69529 : for (int y = 0; y < height; ++y) {
555 67409 : auto px = m_atlas.at((posY + y) * m_atlasWidth + posX + x);
556 67409 : result.bitmap.push_back(px);
557 : }
558 : }
559 : } else {
560 976 : for (int y = 0; y < height; ++y) {
561 42020 : for (int x = 0; x < width; ++x) {
562 41075 : result.bitmap.push_back(m_atlas.at((posY + y) * m_atlasWidth + posX + x));
563 : }
564 : }
565 : }
566 :
567 85 : result.width = it->second.rotated ? height : width;
568 85 : result.height = it->second.rotated ? width : height;
569 85 : result.hash = std::hash<std::string_view> {}({ reinterpret_cast<const char*>(result.bitmap.data()), result.bitmap.size() });
570 :
571 85 : return result;
572 0 : }
|