freetype-commit
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Git][freetype/freetype-demos][gsoc-2022-chariri-final] [ftinspect] Add


From: Charlie Jiang (@cqjjjzr)
Subject: [Git][freetype/freetype-demos][gsoc-2022-chariri-final] [ftinspect] Add "Continuous View".
Date: Mon, 05 Sep 2022 05:17:49 +0000

Charlie Jiang pushed to branch gsoc-2022-chariri-final at FreeType / FreeType Demo Programs

Commits:

  • 21f383fd
    by Charlie Jiang at 2022-09-05T13:16:58+08:00
    [ftinspect] Add "Continuous View".
    
    Most new features in the continuous view are included in the commit, except
    the mouse left (details pane)/right (go to singular) click behaviour.
    
    * src/ftinspect/panels/continuous.cpp, src/ftinspect/panels/continuous.hpp:
      New files, the main continuous tab.
    
    * src/ftinspect/glyphcomponents/glyphcontinuous.cpp,
      src/ftinspect/glyphcomponents/glyphcontinuous.hpp:
      New files, adding the `GlyphContinuous` as the actual canvas for
      continuous rendering.
    
    * src/ftinspect/engine/stringrenderer.cpp,
      src/ftinspect/engine/stringrenderer.hpp:
      New files, adding `StringRenderer` to layout the strings and produce
      glyphs for the canvas to draw.
    
    * src/ftinspect/widgets/charmapcombobox.cpp,
      src/ftinspect/widgets/charmapcombobox.hpp:
      New files, add the `CharMapComboBox` widget.
    
    * src/ftinspect/engine/charmap.cpp,src/ftinspect/engine/charmap.hpp:
      New files, adding `CharMapInfo` class.
    
    * src/ftinspect/engine/engine.cpp, src/ftinspect/engine/engine.hpp:
      Add necessary fields and getters for string rendering.
      Retrieve charmap when loading fonts.
    
    * src/ftinspect/maingui.cpp, src/ftinspect/maingui.hpp:
      Add the continuous view to the main window.
      Call `ContinuousTab::highlightGlyph` when switching from singular to
      continuous view.
    
    * src/ftinspect/CMakeLists.txt, src/ftinspect/meson.build: Updated.
    

16 changed files:

Changes:

  • src/ftinspect/CMakeLists.txt
    ... ... @@ -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
    

  • src/ftinspect/engine/charmap.cpp
    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

  • src/ftinspect/engine/charmap.hpp
    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

  • src/ftinspect/engine/engine.cpp
    ... ... @@ -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
     {
    

  • src/ftinspect/engine/engine.hpp
    ... ... @@ -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_;
    

  • src/ftinspect/engine/stringrenderer.cpp
    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)