Charlie Jiang pushed to branch gsoc-2022-chariri-final at FreeType / FreeType Demo Programs
Commits:
-
28151b8a
by Charlie Jiang at 2022-09-05T13:15:28+08:00
16 changed files:
- src/ftinspect/CMakeLists.txt
- + src/ftinspect/engine/charmap.cpp
- + src/ftinspect/engine/charmap.hpp
- src/ftinspect/engine/engine.cpp
- src/ftinspect/engine/engine.hpp
- + src/ftinspect/engine/stringrenderer.cpp
- + src/ftinspect/engine/stringrenderer.hpp
- + src/ftinspect/glyphcomponents/glyphcontinuous.cpp
- + src/ftinspect/glyphcomponents/glyphcontinuous.hpp
- src/ftinspect/maingui.cpp
- src/ftinspect/maingui.hpp
- src/ftinspect/meson.build
- + src/ftinspect/panels/continuous.cpp
- + src/ftinspect/panels/continuous.hpp
- + src/ftinspect/widgets/charmapcombobox.cpp
- + src/ftinspect/widgets/charmapcombobox.hpp
Changes:
... | ... | @@ -26,8 +26,11 @@ add_executable(ftinspect |
26 | 26 | "engine/paletteinfo.cpp"
|
27 | 27 | "engine/mmgx.cpp"
|
28 | 28 | "engine/fontinfo.cpp"
|
29 | + "engine/stringrenderer.cpp"
|
|
30 | + "engine/charmap.cpp"
|
|
29 | 31 | |
30 | 32 | "glyphcomponents/glyphbitmap.cpp"
|
33 | + "glyphcomponents/glyphcontinuous.cpp"
|
|
31 | 34 | "glyphcomponents/glyphoutline.cpp"
|
32 | 35 | "glyphcomponents/glyphpointnumbers.cpp"
|
33 | 36 | "glyphcomponents/glyphpoints.cpp"
|
... | ... | @@ -38,12 +41,14 @@ add_executable(ftinspect |
38 | 41 | "widgets/tripletselector.cpp"
|
39 | 42 | "widgets/glyphindexselector.cpp"
|
40 | 43 | "widgets/fontsizeselector.cpp"
|
44 | + "widgets/charmapcombobox.cpp"
|
|
41 | 45 | |
42 | 46 | "models/customcomboboxmodels.cpp"
|
43 | 47 | |
44 | 48 | "panels/settingpanel.cpp"
|
45 | 49 | "panels/settingpanelmmgx.cpp"
|
46 | 50 | "panels/singular.cpp"
|
51 | + "panels/continuous.cpp"
|
|
47 | 52 | )
|
48 | 53 | target_link_libraries(ftinspect
|
49 | 54 | Qt5::Core Qt5::Widgets
|
1 | +// charmap.cpp
|
|
2 | + |
|
3 | +// Copyright (C) 2022 by Charlie Jiang.
|
|
4 | + |
|
5 | +#include "charmap.hpp"
|
|
6 | + |
|
7 | +#include <QHash>
|
|
8 | +#include <freetype/freetype.h>
|
|
9 | +#include <freetype/tttables.h>
|
|
10 | + |
|
11 | +namespace
|
|
12 | +{
|
|
13 | +QHash<FT_Encoding, QString>& encodingNames();
|
|
14 | +}
|
|
15 | + |
|
16 | +CharMapInfo::CharMapInfo(int index, FT_CharMap cmap)
|
|
17 | +: index(index), ptr(cmap),
|
|
18 | + encoding(cmap->encoding),
|
|
19 | + platformID(cmap->platform_id),
|
|
20 | + encodingID(cmap->encoding_id),
|
|
21 | + formatID(FT_Get_CMap_Format(cmap)),
|
|
22 | + languageID(FT_Get_CMap_Language_ID(cmap)),
|
|
23 | + maxIndex(-1)
|
|
24 | +{
|
|
25 | + auto& names = encodingNames();
|
|
26 | + auto it = names.find(encoding);
|
|
27 | + if (it == names.end())
|
|
28 | + encodingName = &names[static_cast<FT_Encoding>(FT_ENCODING_OTHER)];
|
|
29 | + else
|
|
30 | + encodingName = &it.value();
|
|
31 | + |
|
32 | + if (encoding != FT_ENCODING_OTHER)
|
|
33 | + maxIndex = computeMaxIndex();
|
|
34 | +}
|
|
35 | + |
|
36 | + |
|
37 | +QString
|
|
38 | +CharMapInfo::stringifyIndex(int code, int idx)
|
|
39 | +{
|
|
40 | + return QString("CharCode: %1 (glyph idx %2)")
|
|
41 | + .arg(stringifyIndexShort(code))
|
|
42 | + .arg(idx);
|
|
43 | +}
|
|
44 | + |
|
45 | + |
|
46 | +QString
|
|
47 | +CharMapInfo::stringifyIndexShort(int code)
|
|
48 | +{
|
|
49 | + return (encoding == FT_ENCODING_UNICODE ? "U+" : "0x")
|
|
50 | + + QString::number(code, 16).rightJustified(4, '0').toUpper();
|
|
51 | +}
|
|
52 | + |
|
53 | + |
|
54 | +int
|
|
55 | +CharMapInfo::computeMaxIndex()
|
|
56 | +{
|
|
57 | + int result;
|
|
58 | + switch (encoding)
|
|
59 | + {
|
|
60 | + case FT_ENCODING_UNICODE:
|
|
61 | + result = maxIndexForFaceAndCharMap(ptr, 0x110000) + 1;
|
|
62 | + break;
|
|
63 | + |
|
64 | + case FT_ENCODING_ADOBE_LATIN_1:
|
|
65 | + case FT_ENCODING_ADOBE_STANDARD:
|
|
66 | + case FT_ENCODING_ADOBE_EXPERT:
|
|
67 | + case FT_ENCODING_ADOBE_CUSTOM:
|
|
68 | + case FT_ENCODING_APPLE_ROMAN:
|
|
69 | + result = 0x100;
|
|
70 | + break;
|
|
71 | + |
|
72 | + /* some fonts use range 0x00-0x100, others have 0xF000-0xF0FF */
|
|
73 | + case FT_ENCODING_MS_SYMBOL:
|
|
74 | + result = maxIndexForFaceAndCharMap(ptr, 0x10000) + 1;
|
|
75 | + break;
|
|
76 | + |
|
77 | + default:
|
|
78 | + // Some encodings can reach > 0x10000, e.g. GB 18030.
|
|
79 | + result = maxIndexForFaceAndCharMap(ptr, 0x110000) + 1;
|
|
80 | + }
|
|
81 | + return result;
|
|
82 | +}
|
|
83 | + |
|
84 | + |
|
85 | +int
|
|
86 | +CharMapInfo::maxIndexForFaceAndCharMap(FT_CharMap charMap,
|
|
87 | + unsigned maxIn)
|
|
88 | +{
|
|
89 | + // code adopted from `ftcommon.c`
|
|
90 | + // This never overflows since no format here exceeds INT_MAX...
|
|
91 | + FT_ULong min = 0, max = maxIn;
|
|
92 | + FT_UInt glyphIndex;
|
|
93 | + FT_Face face = charMap->face;
|
|
94 | + |
|
95 | + if (FT_Set_Charmap(face, charMap))
|
|
96 | + return -1;
|
|
97 | + |
|
98 | + do
|
|
99 | + {
|
|
100 | + FT_ULong mid = (min + max) >> 1;
|
|
101 | + FT_ULong res = FT_Get_Next_Char(face, mid, &glyphIndex);
|
|
102 | + |
|
103 | + if (glyphIndex)
|
|
104 | + min = res;
|
|
105 | + else
|
|
106 | + {
|
|
107 | + max = mid;
|
|
108 | + |
|
109 | + // once moved, it helps to advance min through sparse regions
|
|
110 | + if (min)
|
|
111 | + {
|
|
112 | + res = FT_Get_Next_Char(face, min, &glyphIndex);
|
|
113 | + |
|
114 | + if (glyphIndex)
|
|
115 | + min = res;
|
|
116 | + else
|
|
117 | + max = min; // found it
|
|
118 | + }
|
|
119 | + }
|
|
120 | + } while (max > min);
|
|
121 | + |
|
122 | + return static_cast<int>(max);
|
|
123 | +}
|
|
124 | + |
|
125 | + |
|
126 | +namespace
|
|
127 | +{
|
|
128 | +// Mapping for `FT_Encoding` is placed here since it's only for the charmap.
|
|
129 | +QHash<FT_Encoding, QString> encodingNamesCache;
|
|
130 | +QHash<FT_Encoding, QString>&
|
|
131 | +encodingNames()
|
|
132 | +{
|
|
133 | + if (encodingNamesCache.empty())
|
|
134 | + {
|
|
135 | + encodingNamesCache[static_cast<FT_Encoding>(FT_ENCODING_OTHER)]
|
|
136 | + = "Unknown Encoding";
|
|
137 | + encodingNamesCache[FT_ENCODING_NONE] = "No Encoding";
|
|
138 | + encodingNamesCache[FT_ENCODING_MS_SYMBOL] = "MS Symbol (symb)";
|
|
139 | + encodingNamesCache[FT_ENCODING_UNICODE] = "Unicode (unic)";
|
|
140 | + encodingNamesCache[FT_ENCODING_SJIS] = "Shift JIS (sjis)";
|
|
141 | + encodingNamesCache[FT_ENCODING_PRC] = "PRC/GB 18030 (gb)";
|
|
142 | + encodingNamesCache[FT_ENCODING_BIG5] = "Big5 (big5)";
|
|
143 | + encodingNamesCache[FT_ENCODING_WANSUNG] = "Wansung (wans)";
|
|
144 | + encodingNamesCache[FT_ENCODING_JOHAB] = "Johab (joha)";
|
|
145 | + encodingNamesCache[FT_ENCODING_ADOBE_STANDARD] = "Adobe Standard (ADOB)";
|
|
146 | + encodingNamesCache[FT_ENCODING_ADOBE_EXPERT] = "Adobe Expert (ADBE)";
|
|
147 | + encodingNamesCache[FT_ENCODING_ADOBE_CUSTOM] = "Adobe Custom (ADBC)";
|
|
148 | + encodingNamesCache[FT_ENCODING_ADOBE_LATIN_1] = "Latin 1 (lat1)";
|
|
149 | + encodingNamesCache[FT_ENCODING_OLD_LATIN_2] = "Latin 2 (lat2)";
|
|
150 | + encodingNamesCache[FT_ENCODING_APPLE_ROMAN] = "Apple Roman (armn)";
|
|
151 | + }
|
|
152 | + |
|
153 | + return encodingNamesCache;
|
|
154 | +}
|
|
155 | +}
|
|
156 | + |
|
157 | + |
|
158 | +// end of charmap.cpp |
1 | +// charmap.hpp
|
|
2 | + |
|
3 | +// Copyright (C) 2022 by Charlie Jiang.
|
|
4 | + |
|
5 | +#pragma once
|
|
6 | + |
|
7 | +#include <QString>
|
|
8 | + |
|
9 | +#include <ft2build.h>
|
|
10 | +#include <freetype/freetype.h>
|
|
11 | + |
|
12 | +class Engine;
|
|
13 | + |
|
14 | +#define FT_ENCODING_OTHER 0xFFFE
|
|
15 | +struct CharMapInfo
|
|
16 | +{
|
|
17 | + int index;
|
|
18 | + FT_CharMap ptr;
|
|
19 | + FT_Encoding encoding;
|
|
20 | + unsigned short platformID;
|
|
21 | + unsigned short encodingID;
|
|
22 | + long formatID;
|
|
23 | + unsigned long languageID;
|
|
24 | + QString* encodingName;
|
|
25 | + |
|
26 | + // Actually this shouldn't go here, but for convenience...
|
|
27 | + int maxIndex;
|
|
28 | + |
|
29 | + CharMapInfo(int index, FT_CharMap cmap);
|
|
30 | + |
|
31 | + QString stringifyIndex(int code, int idx);
|
|
32 | + QString stringifyIndexShort(int code);
|
|
33 | + |
|
34 | + |
|
35 | + friend bool
|
|
36 | + operator==(const CharMapInfo& lhs, const CharMapInfo& rhs)
|
|
37 | + {
|
|
38 | + // omitting `ptr` by design!
|
|
39 | + return lhs.index == rhs.index
|
|
40 | + && lhs.encoding == rhs.encoding
|
|
41 | + && lhs.platformID == rhs.platformID
|
|
42 | + && lhs.encodingID == rhs.encodingID
|
|
43 | + && lhs.formatID == rhs.formatID
|
|
44 | + && lhs.languageID == rhs.languageID;
|
|
45 | + }
|
|
46 | + |
|
47 | + |
|
48 | + friend bool
|
|
49 | + operator!=(const CharMapInfo& lhs, const CharMapInfo& rhs)
|
|
50 | + {
|
|
51 | + return !(lhs == rhs);
|
|
52 | + }
|
|
53 | + |
|
54 | + |
|
55 | +private:
|
|
56 | + int computeMaxIndex();
|
|
57 | + static int maxIndexForFaceAndCharMap(FT_CharMap charMap, unsigned max);
|
|
58 | +};
|
|
59 | + |
|
60 | + |
|
61 | +// end of charmap.hpp |
... | ... | @@ -152,6 +152,12 @@ Engine::Engine() |
152 | 152 | // XXX error handling
|
153 | 153 | }
|
154 | 154 | |
155 | + error = FTC_CMapCache_New(cacheManager_, &cmapCache_);
|
|
156 | + if (error)
|
|
157 | + {
|
|
158 | + // XXX error handling
|
|
159 | + }
|
|
160 | +
|
|
155 | 161 | queryEngine();
|
156 | 162 | renderingEngine_
|
157 | 163 | = std::unique_ptr<RenderingEngine>(new RenderingEngine(this));
|
... | ... | @@ -328,6 +334,7 @@ Engine::loadFont(int fontIndex, |
328 | 334 | ftSize_ = NULL;
|
329 | 335 | curFamilyName_ = QString();
|
330 | 336 | curStyleName_ = QString();
|
337 | + curCharMaps_.clear();
|
|
331 | 338 | curSFNTNames_.clear();
|
332 | 339 | }
|
333 | 340 | else
|
... | ... | @@ -345,6 +352,11 @@ Engine::loadFont(int fontIndex, |
345 | 352 | else
|
346 | 353 | fontType_ = FontType_Other;
|
347 | 354 | |
355 | + curCharMaps_.clear();
|
|
356 | + curCharMaps_.reserve(ftFallbackFace_->num_charmaps);
|
|
357 | + for (int i = 0; i < ftFallbackFace_->num_charmaps; i++)
|
|
358 | + curCharMaps_.emplace_back(i, ftFallbackFace_->charmaps[i]);
|
|
359 | + |
|
348 | 360 | SFNTName::get(this, curSFNTNames_);
|
349 | 361 | loadPaletteInfos();
|
350 | 362 | curMMGXState_ = MMGXAxisInfo::get(this, curMMGXAxes_);
|
... | ... | @@ -463,6 +475,58 @@ Engine::currentFontFixedSizes() |
463 | 475 | }
|
464 | 476 | |
465 | 477 | |
478 | +int
|
|
479 | +Engine::currentFontFirstUnicodeCharMap()
|
|
480 | +{
|
|
481 | + auto& charmaps = currentFontCharMaps();
|
|
482 | + for (auto& cmap : charmaps)
|
|
483 | + if (cmap.encoding == FT_ENCODING_UNICODE)
|
|
484 | + return cmap.index;
|
|
485 | + return -1;
|
|
486 | +}
|
|
487 | + |
|
488 | + |
|
489 | +unsigned
|
|
490 | +Engine::glyphIndexFromCharCode(int code, int charMapIndex)
|
|
491 | +{
|
|
492 | + if (charMapIndex < 0)
|
|
493 | + return code;
|
|
494 | + return FTC_CMapCache_Lookup(cmapCache_, scaler_.face_id, charMapIndex, code);
|
|
495 | +}
|
|
496 | + |
|
497 | + |
|
498 | +FT_Pos
|
|
499 | +Engine::currentFontTrackingKerning(int degree)
|
|
500 | +{
|
|
501 | + if (!ftSize_)
|
|
502 | + return 0;
|
|
503 | + |
|
504 | + FT_Pos result;
|
|
505 | + // this function needs and returns points, not pixels
|
|
506 | + if (!FT_Get_Track_Kerning(ftSize_->face,
|
|
507 | + static_cast<FT_Fixed>(scaler_.width) << 10,
|
|
508 | + -degree,
|
|
509 | + &result))
|
|
510 | + {
|
|
511 | + result = static_cast<FT_Pos>((result / 1024.0 * scaler_.x_res) / 72.0);
|
|
512 | + return result;
|
|
513 | + }
|
|
514 | + return 0;
|
|
515 | +}
|
|
516 | + |
|
517 | + |
|
518 | +FT_Vector
|
|
519 | +Engine::currentFontKerning(int glyphIndex,
|
|
520 | + int prevIndex)
|
|
521 | +{
|
|
522 | + FT_Vector kern = {0, 0};
|
|
523 | + FT_Get_Kerning(ftSize_->face,
|
|
524 | + prevIndex, glyphIndex,
|
|
525 | + FT_KERNING_UNFITTED, &kern);
|
|
526 | + return kern;
|
|
527 | +}
|
|
528 | + |
|
529 | + |
|
466 | 530 | std::pair<int, int>
|
467 | 531 | Engine::currentSizeAscDescPx()
|
468 | 532 | {
|
... | ... | @@ -569,6 +633,19 @@ Engine::loadGlyphWithoutUpdate(int glyphIndex, |
569 | 633 | }
|
570 | 634 | |
571 | 635 | |
636 | +FT_Size_Metrics const&
|
|
637 | +Engine::currentFontMetrics()
|
|
638 | +{
|
|
639 | + return ftSize_->metrics;
|
|
640 | +}
|
|
641 | + |
|
642 | +FT_GlyphSlot
|
|
643 | +Engine::currentFaceSlot()
|
|
644 | +{
|
|
645 | + return ftSize_->face->glyph;
|
|
646 | +}
|
|
647 | + |
|
648 | + |
|
572 | 649 | bool
|
573 | 650 | Engine::renderReady()
|
574 | 651 | {
|
... | ... | @@ -6,12 +6,13 @@ |
6 | 6 | #pragma once
|
7 | 7 | |
8 | 8 | #include "fontfilemanager.hpp"
|
9 | - |
|
10 | 9 | #include "paletteinfo.hpp"
|
11 | 10 | #include "fontinfo.hpp"
|
12 | 11 | #include "mmgx.hpp"
|
13 | 12 | #include "rendering.hpp"
|
13 | +#include "charmap.hpp"
|
|
14 | 14 | |
15 | +#include <vector>
|
|
15 | 16 | #include <memory>
|
16 | 17 | #include <utility>
|
17 | 18 | #include <QString>
|
... | ... | @@ -109,6 +110,9 @@ public: |
109 | 110 | // (for current fonts)
|
110 | 111 | FT_Face currentFallbackFtFace() { return ftFallbackFace_; }
|
111 | 112 | FT_Size currentFtSize() { return ftSize_; }
|
113 | + FT_Size_Metrics const& currentFontMetrics();
|
|
114 | + FT_GlyphSlot currentFaceSlot();
|
|
115 | + |
|
112 | 116 | bool renderReady(); // Can we render bitmaps? (implys `fontValid`)
|
113 | 117 | bool fontValid(); // Is the current font valid (valid font may be unavailable
|
114 | 118 | // to render, such as non-scalable font with invalid sizes)
|
... | ... | @@ -124,6 +128,7 @@ public: |
124 | 128 | MMGXState currentFontMMGXState() { return curMMGXState_; }
|
125 | 129 | std::vector<MMGXAxisInfo>& currentFontMMGXAxes() { return curMMGXAxes_; }
|
126 | 130 | std::vector<SFNTName>& currentFontSFNTNames() { return curSFNTNames_; }
|
131 | + std::vector<CharMapInfo>& currentFontCharMaps() { return curCharMaps_; }
|
|
127 | 132 | |
128 | 133 | QString glyphName(int glyphIndex);
|
129 | 134 | long numberOfFaces(int fontIndex);
|
... | ... | @@ -137,13 +142,21 @@ public: |
137 | 142 | bool currentFontHasColorLayers();
|
138 | 143 | std::vector<int> currentFontFixedSizes();
|
139 | 144 | |
145 | + int currentFontFirstUnicodeCharMap();
|
|
146 | + // Note: the current font face must be properly set
|
|
147 | + unsigned glyphIndexFromCharCode(int code, int charMapIndex);
|
|
148 | + FT_Pos currentFontTrackingKerning(int degree);
|
|
149 | + FT_Vector currentFontKerning(int glyphIndex, int prevIndex);
|
|
140 | 150 | std::pair<int, int> currentSizeAscDescPx();
|
141 | 151 | |
142 | 152 | // (settings)
|
143 | 153 | int dpi() { return dpi_; }
|
154 | + double pointSize() { return pointSize_; }
|
|
144 | 155 | FTC_ImageType imageType() { return &imageType_; }
|
145 | 156 | bool antiAliasingEnabled() { return antiAliasingEnabled_; }
|
157 | + bool doHinting() { return doHinting_; }
|
|
146 | 158 | bool embeddedBitmapEnabled() { return embeddedBitmap_; }
|
159 | + bool lcdUsingSubPixelPositioning() { return lcdSubPixelPositioning_; }
|
|
147 | 160 | bool useColorLayer() { return useColorLayer_; }
|
148 | 161 | int paletteIndex() { return paletteIndex_; }
|
149 | 162 | FT_Render_Mode
|
... | ... | @@ -179,8 +192,9 @@ public: |
179 | 192 | void setEmbeddedBitmapEnabled(bool enabled) { embeddedBitmap_ = enabled; }
|
180 | 193 | void setUseColorLayer(bool colorLayer) { useColorLayer_ = colorLayer; }
|
181 | 194 | void setPaletteIndex(int index) { paletteIndex_ = index; }
|
195 | + void setLCDSubPixelPositioning(bool sp) { lcdSubPixelPositioning_ = sp; }
|
|
196 | +
|
|
182 | 197 | // (settings without backing fields)
|
183 | - |
|
184 | 198 | // Note: These 3 functions now takes actual mode/version from FreeType,
|
185 | 199 | // instead of values from enum in MainGUI!
|
186 | 200 | void setLcdFilter(FT_LcdFilter filter);
|
... | ... | @@ -210,6 +224,7 @@ private: |
210 | 224 | QString curFamilyName_;
|
211 | 225 | QString curStyleName_;
|
212 | 226 | int curNumGlyphs_ = -1;
|
227 | + std::vector<CharMapInfo> curCharMaps_;
|
|
213 | 228 | std::vector<PaletteInfo> curPaletteInfos_;
|
214 | 229 | MMGXState curMMGXState_ = MMGXState::NoMMGX;
|
215 | 230 | std::vector<MMGXAxisInfo> curMMGXAxes_;
|
... | ... | @@ -220,6 +235,7 @@ private: |
220 | 235 | FTC_Manager cacheManager_;
|
221 | 236 | FTC_ImageCache imageCache_;
|
222 | 237 | FTC_SBitCache sbitsCache_;
|
238 | + FTC_CMapCache cmapCache_;
|
|
223 | 239 | EngineDefaultValues engineDefaults_;
|
224 | 240 | |
225 | 241 | // settings
|
... | ... | @@ -249,6 +265,7 @@ private: |
249 | 265 | bool useColorLayer_;
|
250 | 266 | int paletteIndex_ = -1;
|
251 | 267 | int antiAliasingTarget_;
|
268 | + bool lcdSubPixelPositioning_;
|
|
252 | 269 | int renderMode_;
|
253 | 270 | |
254 | 271 | unsigned long loadFlags_;
|
1 | +// stringrenderer.cpp
|
|
2 | + |
|
3 | +// Copyright (C) 2022 by Charlie Jiang.
|
|
4 | + |
|
5 | +#include "stringrenderer.hpp"
|
|
6 | + |
|
7 | +#include "engine.hpp"
|
|
8 | + |
|
9 | +#include <cmath>
|
|
10 | +#include <QTextCodec>
|
|
11 | + |
|
12 | + |
|
13 | +StringRenderer::StringRenderer(Engine* engine)
|
|
14 | +: engine_(engine)
|
|
15 | +{
|
|
16 | +}
|
|
17 | + |
|
18 | + |
|
19 | +StringRenderer::~StringRenderer()
|
|
20 | +{
|
|
21 | + clearActive();
|
|
22 | +}
|
|
23 | + |
|
24 | + |
|
25 | +void
|
|
26 | +StringRenderer::setCharMapIndex(int charMapIndex,
|
|
27 | + int limitIndex)
|
|
28 | +{
|
|
29 | + auto& charMaps = engine_->currentFontCharMaps();
|
|
30 | + if (charMapIndex < 0
|
|
31 | + || static_cast<unsigned>(charMapIndex) >= charMaps.size())
|
|
32 | + charMapIndex = -1;
|
|
33 | + |
|
34 | + charMapIndex_ = charMapIndex;
|
|
35 | + limitIndex_ = limitIndex;
|
|
36 | +}
|
|
37 | + |
|
38 | + |
|
39 | +void
|
|
40 | +StringRenderer::setRotation(double rotation)
|
|
41 | +{
|
|
42 | + rotation_ = rotation;
|
|
43 | + |
|
44 | + if (rotation <= -180)
|
|
45 | + rotation += 360;
|
|
46 | + if (rotation > 180)
|
|
47 | + rotation -= 360;
|
|
48 | + |
|
49 | + if (rotation == 0)
|
|
50 | + {
|
|
51 | + matrixEnabled_ = false;
|
|
52 | + return;
|
|
53 | + }
|
|
54 | + |
|
55 | + matrixEnabled_ = true;
|
|
56 | + double radian = rotation * 3.14159265 / 180.0;
|
|
57 | + auto cosinus = static_cast<FT_Fixed>(cos(radian) * 65536.0);
|
|
58 | + auto sinus = static_cast<FT_Fixed>(sin(radian) * 65536.0);
|
|
59 | + |
|
60 | + matrix_.xx = cosinus;
|
|
61 | + matrix_.yx = sinus;
|
|
62 | + matrix_.xy = -sinus;
|
|
63 | + matrix_.yy = cosinus;
|
|
64 | +}
|
|
65 | + |
|
66 | + |
|
67 | +void
|
|
68 | +StringRenderer::setKerning(bool kerning)
|
|
69 | +{
|
|
70 | + if (kerning)
|
|
71 | + {
|
|
72 | + kerningMode_ = KM_Smart;
|
|
73 | + kerningDegree_ = KD_Medium;
|
|
74 | + }
|
|
75 | + else
|
|
76 | + {
|
|
77 | + kerningMode_ = KM_None;
|
|
78 | + kerningDegree_ = KD_None;
|
|
79 | + }
|
|
80 | +}
|
|
81 | + |
|
82 | + |
|
83 | +void
|
|
84 | +StringRenderer::reloadAll()
|
|
85 | +{
|
|
86 | + clearActive(usingString_); // if "All Glyphs", then do a complete wipe
|
|
87 | + if (usingString_)
|
|
88 | + reloadGlyphIndices();
|
|
89 | +}
|
|
90 | + |
|
91 | + |
|
92 | +void
|
|
93 | +StringRenderer::reloadGlyphs()
|
|
94 | +{
|
|
95 | + clearActive(true);
|
|
96 | +}
|
|
97 | + |
|
98 | + |
|
99 | +void
|
|
100 | +StringRenderer::setUseString(QString const& string)
|
|
101 | +{
|
|
102 | + clearActive(); // clear existing
|
|
103 | + usingString_ = true;
|
|
104 | + |
|
105 | + long long totalCount = 0;
|
|
106 | + for (uint ch : string.toUcs4())
|
|
107 | + {
|
|
108 | + activeGlyphs_.emplace_back();
|
|
109 | + auto& it = activeGlyphs_.back();
|
|
110 | + it.charCodeUcs4 = it.charCode = static_cast<int>(ch);
|
|
111 | + it.glyphIndex = 0;
|
|
112 | + ++totalCount;
|
|
113 | + if (totalCount >= INT_MAX) // Prevent overflow
|
|
114 | + break;
|
|
115 | + }
|
|
116 | + reloadGlyphIndices();
|
|
117 | +}
|
|
118 | + |
|
119 | + |
|
120 | +void
|
|
121 | +StringRenderer::setUseAllGlyphs()
|
|
122 | +{
|
|
123 | + if (usingString_)
|
|
124 | + clearActive();
|
|
125 | + usingString_ = false;
|
|
126 | +}
|
|
127 | + |
|
128 | + |
|
129 | +void
|
|
130 | +StringRenderer::reloadGlyphIndices()
|
|
131 | +{
|
|
132 | + if (!usingString_)
|
|
133 | + return;
|
|
134 | + int charMapIndex = charMapIndex_;
|
|
135 | + auto& charMaps = engine_->currentFontCharMaps();
|
|
136 | + if (charMaps.empty())
|
|
137 | + return;
|
|
138 | + if (charMapIndex < 0
|
|
139 | + || static_cast<unsigned>(charMapIndex) >= charMaps.size())
|
|
140 | + charMapIndex = engine_->currentFontFirstUnicodeCharMap();
|
|
141 | + if (charMapIndex < 0
|
|
142 | + || static_cast<unsigned>(charMapIndex) >= charMaps.size())
|
|
143 | + charMapIndex = 0;
|
|
144 | + auto encoding = charMaps[charMapIndex].encoding;
|
|
145 | + |
|
146 | + if (charMapIndex < 0)
|
|
147 | + return;
|
|
148 | + for (auto& ctx : activeGlyphs_)
|
|
149 | + {
|
|
150 | + if (encoding != FT_ENCODING_UNICODE)
|
|
151 | + ctx.charCode = convertCharEncoding(ctx.charCodeUcs4, encoding);
|
|
152 | + |
|
153 | + auto index = engine_->glyphIndexFromCharCode(ctx.charCode, charMapIndex);
|
|
154 | + ctx.glyphIndex = static_cast<int>(index);
|
|
155 | + }
|
|
156 | +}
|
|
157 | + |
|
158 | + |
|
159 | +void
|
|
160 | +StringRenderer::prepareRendering()
|
|
161 | +{
|
|
162 | + engine_->reloadFont();
|
|
163 | + if (!engine_->renderReady())
|
|
164 | + return;
|
|
165 | + engine_->loadPalette();
|
|
166 | + if (kerningDegree_ != KD_None)
|
|
167 | + trackingKerning_ = engine_->currentFontTrackingKerning(kerningDegree_);
|
|
168 | + else
|
|
169 | + trackingKerning_ = 0;
|
|
170 | +}
|
|
171 | + |
|
172 | + |
|
173 | +void
|
|
174 | +StringRenderer::loadSingleContext(GlyphContext* ctx,
|
|
175 | + GlyphContext* prev)
|
|
176 | +{
|
|
177 | + if (ctx->cacheNode)
|
|
178 | + {
|
|
179 | + FTC_Node_Unref(ctx->cacheNode, engine_->cacheManager());
|
|
180 | + ctx->cacheNode = NULL;
|
|
181 | + }
|
|
182 | + else if (ctx->glyph)
|
|
183 | + FT_Done_Glyph(ctx->glyph); // when caching isn't used
|
|
184 | + |
|
185 | + // TODO use FTC?
|
|
186 | + |
|
187 | + // After `prepareRendering`, current size/face is properly set
|
|
188 | + FT_GlyphSlot slot = engine_->currentFaceSlot();
|
|
189 | + if (engine_->loadGlyphIntoSlotWithoutCache(ctx->glyphIndex) != 0)
|
|
190 | + {
|
|
191 | + ctx->glyph = NULL;
|
|
192 | + return;
|
|
193 | + }
|
|
194 | + if (FT_Get_Glyph(slot, &ctx->glyph) != 0)
|
|
195 | + {
|
|
196 | + ctx->glyph = NULL;
|
|
197 | + return;
|
|
198 | + }
|
|
199 | + auto& metrics = slot->metrics;
|
|
200 | + //ctx->glyph = engine_->loadGlyphWithoutUpdate(ctx->glyphIndex,
|
|
201 | + // &ctx->cacheNode);
|
|
202 | + |
|
203 | + if (!ctx->glyph)
|
|
204 | + return;
|
|
205 | + |
|
206 | + ctx->vvector.x = metrics.vertBearingX - metrics.horiBearingX;
|
|
207 | + ctx->vvector.y = -metrics.vertBearingY - metrics.horiBearingY;
|
|
208 | + |
|
209 | + ctx->vadvance.x = 0;
|
|
210 | + ctx->vadvance.y = -metrics.vertAdvance;
|
|
211 | + |
|
212 | + ctx->lsbDelta = slot->lsb_delta;
|
|
213 | + ctx->rsbDelta = slot->rsb_delta;
|
|
214 | + |
|
215 | + ctx->hadvance.x = metrics.horiAdvance;
|
|
216 | + ctx->hadvance.y = 0;
|
|
217 | + |
|
218 | + if (lsbRsbDeltaEnabled_ && engine_->lcdUsingSubPixelPositioning())
|
|
219 | + ctx->hadvance.x += ctx->lsbDelta - ctx->rsbDelta;
|
|
220 | + prev->hadvance.x += trackingKerning_;
|
|
221 | + |
|
222 | + if (kerningMode_ != KM_None)
|
|
223 | + {
|
|
224 | + FT_Vector kern = engine_->currentFontKerning(ctx->glyphIndex,
|
|
225 | + prev->glyphIndex);
|
|
226 | + |
|
227 | + prev->hadvance.x += kern.x;
|
|
228 | + prev->hadvance.y += kern.y;
|
|
229 | + }
|
|
230 | + |
|
231 | + if (!engine_->lcdUsingSubPixelPositioning()
|
|
232 | + && lsbRsbDeltaEnabled_)
|
|
233 | + {
|
|
234 | + if (prev->rsbDelta - ctx->lsbDelta > 32)
|
|
235 | + prev->hadvance.x -= 64;
|
|
236 | + else if (prev->rsbDelta - ctx->lsbDelta < -31)
|
|
237 | + prev->hadvance.x += 64;
|
|
238 | + }
|
|
239 | + |
|
240 | + if (!engine_->lcdUsingSubPixelPositioning() && engine_->doHinting())
|
|
241 | + {
|
|
242 | + prev->hadvance.x = (prev->hadvance.x + 32) & -64;
|
|
243 | + prev->hadvance.y = (prev->hadvance.y + 32) & -64;
|
|
244 | + }
|
|
245 | +}
|
|
246 | + |
|
247 | + |
|
248 | +void
|
|
249 | +StringRenderer::loadStringGlyphs()
|
|
250 | +{
|
|
251 | + if (!usingString_)
|
|
252 | + return;
|
|
253 | + GlyphContext* prev = &tempGlyphContext_; // = empty
|
|
254 | + tempGlyphContext_ = {};
|
|
255 | + |
|
256 | + for (auto& ctx : activeGlyphs_)
|
|
257 | + {
|
|
258 | + loadSingleContext(&ctx, prev);
|
|
259 | + prev = &ctx;
|
|
260 | + }
|
|
261 | + |
|
262 | + glyphCacheValid_ = true;
|
|
263 | +}
|
|
264 | + |
|
265 | + |
|
266 | +int
|
|
267 | +StringRenderer::prepareLine(int offset,
|
|
268 | + int lineWidth,
|
|
269 | + FT_Vector& outActualLineWidth,
|
|
270 | + int nonSpacingPlaceholder,
|
|
271 | + bool handleMultiLine)
|
|
272 | +{
|
|
273 | + int totalCount = 0;
|
|
274 | + outActualLineWidth = {0, 0};
|
|
275 | + if (!usingString_) // All glyphs
|
|
276 | + {
|
|
277 | + // The thing gets a little complicated when we're using "All Glyphs" mode
|
|
278 | + // The input sequence is actually infinite
|
|
279 | + // so we have to combine loading glyph into rendering, and can't preload
|
|
280 | + // all glyphs
|
|
281 | + |
|
282 | + // TODO: Low performance when the begin index is large.
|
|
283 | + // TODO: Optimize: use a sparse vector...!
|
|
284 | + // The problem is that when doing a `list::resize`, the ctor is called
|
|
285 | + // for unnecessarily many times.
|
|
286 | + tempGlyphContext_ = {};
|
|
287 | + for (unsigned n = offset; n < static_cast<unsigned>(limitIndex_);)
|
|
288 | + {
|
|
289 | + if (activeGlyphs_.capacity() <= n)
|
|
290 | + activeGlyphs_.reserve(static_cast<size_t>(n) * 2);
|
|
291 | + if (activeGlyphs_.size() <= n)
|
|
292 | + activeGlyphs_.resize(n + 1);
|
|
293 | + |
|
294 | + auto& ctx = activeGlyphs_[n];
|
|
295 | + ctx.charCode = static_cast<int>(n);
|
|
296 | + ctx.glyphIndex = static_cast<int>(
|
|
297 | + engine_->glyphIndexFromCharCode(static_cast<int>(n), charMapIndex_));
|
|
298 | + |
|
299 | + auto prev = n == 0 ? &tempGlyphContext_ : &activeGlyphs_[n - 1];
|
|
300 | + if (!ctx.glyph)
|
|
301 | + loadSingleContext(&ctx, prev);
|
|
302 | + |
|
303 | + // In All Glyphs mode, a red placeholder should be drawn for non-spacing
|
|
304 | + // glyphs (e.g. the stress mark)
|
|
305 | + auto actualAdvanceX = ctx.hadvance.x ? ctx.hadvance.x
|
|
306 | + : nonSpacingPlaceholder << 6;
|
|
307 | + if (outActualLineWidth.x + actualAdvanceX > lineWidth)
|
|
308 | + break;
|
|
309 | + outActualLineWidth.x += actualAdvanceX;
|
|
310 | + outActualLineWidth.y += ctx.hadvance.y;
|
|
311 | + ++n;
|
|
312 | + ++totalCount;
|
|
313 | + }
|
|
314 | + }
|
|
315 | + else // strings
|
|
316 | + {
|
|
317 | + if (!glyphCacheValid_)
|
|
318 | + {
|
|
319 | + clearActive(true);
|
|
320 | + loadStringGlyphs();
|
|
321 | + }
|
|
322 | + |
|
323 | + for (unsigned n = offset; n < activeGlyphs_.size();)
|
|
324 | + {
|
|
325 | + auto& ctx = activeGlyphs_[n];
|
|
326 | + |
|
327 | + if (handleMultiLine && ctx.charCode == '\n')
|
|
328 | + {
|
|
329 | + totalCount += 1; // Break here.
|
|
330 | + break;
|
|
331 | + }
|
|
332 | + |
|
333 | + if (repeated_) // if repeated, we must stop when we touch the end of line
|
|
334 | + {
|
|
335 | + if (outActualLineWidth.x + ctx.hadvance.x > lineWidth)
|
|
336 | + break;
|
|
337 | + outActualLineWidth.x += ctx.hadvance.x;
|
|
338 | + outActualLineWidth.y += ctx.hadvance.y;
|
|
339 | + ++n;
|
|
340 | + n %= static_cast<int>(activeGlyphs_.size()); // safe
|
|
341 | + }
|
|
342 | + else if (vertical_)
|
|
343 | + {
|
|
344 | + outActualLineWidth.x += ctx.vadvance.x;
|
|
345 | + outActualLineWidth.y += ctx.vadvance.y;
|
|
346 | + ++n;
|
|
347 | + }
|
|
348 | + else
|
|
349 | + {
|
|
350 | + outActualLineWidth.x += ctx.hadvance.x;
|
|
351 | + outActualLineWidth.y += ctx.hadvance.y;
|
|
352 | + ++n;
|
|
353 | + }
|
|
354 | + ++totalCount;
|
|
355 | + }
|
|
356 | + }
|
|
357 | + return totalCount;
|
|
358 | +}
|
|
359 | + |
|
360 | + |
|
361 | +int
|
|
362 | +StringRenderer::render(int width,
|
|
363 | + int height,
|
|
364 | + int offset)
|
|
365 | +{
|
|
366 | + engine_->reloadFont();
|
|
367 | + |
|
368 | + if (usingString_)
|
|
369 | + offset = 0;
|
|
370 | + if (!usingString_ && limitIndex_ <= 0)
|
|
371 | + return 0;
|
|
372 | + if (!engine_->fontValid())
|
|
373 | + return 0;
|
|
374 | + |
|
375 | + auto initialOffset = offset;
|
|
376 | + |
|
377 | + // Separated into 3 modes:
|
|
378 | + // Waterfall, fill the whole canvas and only single string.
|
|
379 | + if (waterfall_)
|
|
380 | + {
|
|
381 | + // Waterfall
|
|
382 | + |
|
383 | + vertical_ = false;
|
|
384 | + // They're only effective for non-bitmap-only (scalable) fonts!
|
|
385 | + auto originalSize = static_cast<int>(engine_->pointSize() * 64);
|
|
386 | + auto ptSize = originalSize;
|
|
387 | + auto ptHeight = 64 * 72 * height / engine_->dpi();
|
|
388 | + int step = 0;
|
|
389 | + |
|
390 | + auto bitmapOnly = engine_->currentFontBitmapOnly();
|
|
391 | + auto fixedSizes = engine_->currentFontFixedSizes();
|
|
392 | + std::sort(fixedSizes.begin(), fixedSizes.end());
|
|
393 | + auto fixedSizesIter = fixedSizes.begin();
|
|
394 | + |
|
395 | + if (waterfallStart_ <= 0)
|
|
396 | + {
|
|
397 | + // auto
|
|
398 | + step = (originalSize * originalSize / ptHeight + 64) |