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 04:13:04 +0000

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

Commits:

  • fcb31167
    by Charlie Jiang at 2022-09-05T12:11:13+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) & ~63;
    
    399
    +      ptSize = ptSize - step * (ptSize / step); // modulo
    
    400
    +      ptSize += step;
    
    401
    +    }
    
    402
    +    else if (!bitmapOnly)
    
    403
    +    {
    
    404
    +      ptSize = static_cast<int>(waterfallStart_ * 64.0) & ~31;
    
    405
    +      // we first get a ratio since height & ppem are near proportional...
    
    406
    +      // 64.0 is somewhat a magic reference number
    
    407
    +      engine_->setSizeByPoint(64.0);
    
    408
    +      engine_->reloadFont();
    
    409
    +      if (!engine_->renderReady())
    
    410
    +        return -1;
    
    411
    +      auto pixelActual = engine_->currentFontMetrics().height >> 6;
    
    412
    +
    
    413
    +      auto heightPt = height * 64.0 / pixelActual;
    
    414
    +
    
    415
    +      if (waterfallEnd_ < waterfallStart_)
    
    416
    +        waterfallEnd_ = waterfallStart_ + 1;
    
    417
    +
    
    418
    +      auto n = heightPt * 2 / (waterfallStart_ + waterfallEnd_);
    
    419
    +      auto stepTemp = (waterfallEnd_ - waterfallStart_) / (n + 1);
    
    420
    +      // rounding to 0.25
    
    421
    +      step = static_cast<int>(std::round(stepTemp * 4)) * 16 & ~15;
    
    422
    +      if (step == 0)
    
    423
    +        step = 16; // 0.25 pt
    
    424
    +    }
    
    425
    +
    
    426
    +    int y = 0;
    
    427
    +    // no position param in "All Glyphs" or repeated mode
    
    428
    +    int x = static_cast<int>((usingString_ && !repeated_) ? (width * position_)
    
    429
    +                                                          : 0);
    
    430
    +    int count = 0;
    
    431
    +
    
    432
    +    while (true)
    
    433
    +    {
    
    434
    +      if (!bitmapOnly)
    
    435
    +        engine_->setSizeByPoint(ptSize / 64.0);
    
    436
    +      else
    
    437
    +      {
    
    438
    +        if (fixedSizesIter == fixedSizes.end())
    
    439
    +          break;
    
    440
    +        engine_->setSizeByPixel(*fixedSizesIter);
    
    441
    +      }
    
    442
    +      clearActive(true);
    
    443
    +      prepareRendering(); // set size/face for engine, so metrics are valid
    
    444
    +      auto& metrics = engine_->currentFontMetrics();
    
    445
    +      
    
    446
    +      y += static_cast<int>(metrics.height >> 6) + 1;
    
    447
    +      if (y >= height && !bitmapOnly)
    
    448
    +        break;
    
    449
    +
    
    450
    +      loadStringGlyphs();
    
    451
    +      auto lcount = renderLine(x, y + static_cast<int>(metrics.descender >> 6),
    
    452
    +                               width, height,
    
    453
    +                               offset);
    
    454
    +      count = std::max(count, lcount);
    
    455
    +
    
    456
    +      if (!bitmapOnly)
    
    457
    +      {
    
    458
    +        if (step == 0)
    
    459
    +          break;
    
    460
    +        ptSize += step;
    
    461
    +      }
    
    462
    +      else
    
    463
    +        ++fixedSizesIter;
    
    464
    +    }
    
    465
    +    engine_->setSizeByPoint(originalSize / 64.0);
    
    466
    +
    
    467
    +    return count;
    
    468
    +  }
    
    469
    +  // end of waterfall
    
    470
    +
    
    471
    +  if (repeated_ || !usingString_)
    
    472
    +  {
    
    473
    +    // Fill the whole canvas (string repeated or all glyphs)
    
    474
    +
    
    475
    +    prepareRendering();
    
    476
    +    if (!engine_->renderReady())
    
    477
    +      return 0;
    
    478
    +    auto& metrics = engine_->currentFontMetrics();
    
    479
    +    auto y = 4 + static_cast<int>(metrics.ascender >> 6);
    
    480
    +    auto stepY = static_cast<int>(metrics.height >> 6) + 1;
    
    481
    +    auto limitY = height + static_cast<int>(metrics.descender >> 6);
    
    482
    +
    
    483
    +    // Only care about multiline when in string mode
    
    484
    +    for (; y < limitY; y += stepY)
    
    485
    +    {
    
    486
    +      offset = renderLine(0, y, width, height, offset, usingString_);
    
    487
    +      // For repeating
    
    488
    +      if (usingString_ && repeated_ && !activeGlyphs_.empty())
    
    489
    +        offset %= static_cast<int>(activeGlyphs_.size());
    
    490
    +    }
    
    491
    +    if (!usingString_) // only return count for All Glyphs mode.
    
    492
    +      return offset - initialOffset;
    
    493
    +    return 0;
    
    494
    +  }
    
    495
    +
    
    496
    +  // Single string
    
    497
    +  prepareRendering();
    
    498
    +  if (!engine_->renderReady())
    
    499
    +    return 0;
    
    500
    +
    
    501
    +  auto& metrics = engine_->currentFontMetrics();
    
    502
    +  auto x = static_cast<int>(width * position_);
    
    503
    +  // Anchor at top-left in vertical mode, at the center in horizontal mode
    
    504
    +  auto y = vertical_ ? 0 : (height / 2);
    
    505
    +  auto stepY = static_cast<int>(metrics.height >> 6) + 1;
    
    506
    +  y += 4 + static_cast<int>(metrics.ascender >> 6);
    
    507
    +
    
    508
    +  while (offset < static_cast<int>(activeGlyphs_.size()))
    
    509
    +  {
    
    510
    +    offset = renderLine(x, y, width, height, offset, true);
    
    511
    +    y += stepY;
    
    512
    +  }
    
    513
    +  return offset - initialOffset;
    
    514
    +}
    
    515
    +
    
    516
    +
    
    517
    +int
    
    518
    +StringRenderer::renderLine(int x,
    
    519
    +                           int y,
    
    520
    +                           int width,
    
    521
    +                           int height,
    
    522
    +                           int offset,
    
    523
    +                           bool handleMultiLine)
    
    524
    +{
    
    525
    +  if (x < 0 || y < 0 || x > width || y > height)
    
    526
    +    return 0;
    
    527
    +
    
    528
    +  y = height - y; // change to Cartesian coordinates
    
    529
    +
    
    530
    +  FT_Vector pen = { 0, 0 };
    
    531
    +  FT_Vector advance;
    
    532
    +  auto nonSpacingPlaceholder = engine_->currentFontMetrics().y_ppem / 2 + 2;
    
    533
    +
    
    534
    +  // When in "All Glyphs"  mode, no vertical support.
    
    535
    +  if (repeated_ || !usingString_)
    
    536
    +    vertical_ = false; // TODO: Support vertical + repeated
    
    537
    +
    
    538
    +  int lineLength = 64 * (vertical_ ? height : width);
    
    539
    +
    
    540
    +  // first prepare the line & determine the line length
    
    541
    +  int totalCount = prepareLine(offset, lineLength, pen, 
    
    542
    +                               nonSpacingPlaceholder, handleMultiLine);
    
    543
    +
    
    544
    +  // round to control initial pen position and preserve hinting...
    
    545
    +  // pen.x, y is the actual length now, and we multiple it by pos
    
    546
    +  auto centerFixed = static_cast<int>(0x10000 * position_);
    
    547
    +  if (!usingString_ || repeated_)
    
    548
    +    centerFixed = 0;
    
    549
    +  pen.x = FT_MulFix(pen.x, centerFixed) & ~63;
    
    550
    +  pen.y = FT_MulFix(pen.y, centerFixed) & ~63;
    
    551
    +
    
    552
    +  // ... unless rotating; XXX sbits
    
    553
    +  if (matrixEnabled_)
    
    554
    +    FT_Vector_Transform(&pen, &matrix_);
    
    555
    +
    
    556
    +  // get pen position: penPos = center - pos * width
    
    557
    +  pen.x = (x << 6) - pen.x;
    
    558
    +  pen.y = (y << 6) - pen.y;
    
    559
    +
    
    560
    +  // Need to transform the coord back to normal coord system
    
    561
    +  lineBeginCallback_({ (pen.x >> 6), 
    
    562
    +                       height - (pen.y >> 6) },
    
    563
    +                     engine_->pointSize());
    
    564
    +
    
    565
    +  for (int i = offset; i < totalCount + offset; i++)
    
    566
    +  {
    
    567
    +    auto& ctx = activeGlyphs_[i % activeGlyphs_.size()];
    
    568
    +    if (handleMultiLine && ctx.charCode == '\n')
    
    569
    +      continue; // skip \n
    
    570
    +    FT_Glyph image = NULL; // Remember to clean up
    
    571
    +    FT_BBox bbox;
    
    572
    +
    
    573
    +    if (!ctx.glyph)
    
    574
    +      continue;
    
    575
    +
    
    576
    +    advance = vertical_ ? ctx.vadvance : ctx.hadvance;
    
    577
    +
    
    578
    +    QRect rect;
    
    579
    +    QImage* colorLayerImage
    
    580
    +      = engine_->renderingEngine()->tryDirectRenderColorLayers(ctx.glyphIndex,
    
    581
    +                                                               &rect, true);
    
    582
    +
    
    583
    +    if (colorLayerImage)
    
    584
    +    {
    
    585
    +      FT_Vector penPos = { (pen.x >> 6), height - (pen.y >> 6) };
    
    586
    +      renderImageCallback_(colorLayerImage, rect, penPos, advance, ctx);
    
    587
    +    }
    
    588
    +    else
    
    589
    +    {
    
    590
    +      // copy the glyph because we're doing manipulation
    
    591
    +      auto error = FT_Glyph_Copy(ctx.glyph, &image);
    
    592
    +      if (error)
    
    593
    +        continue;
    
    594
    +
    
    595
    +      glyphPreprocessCallback_(&image);
    
    596
    +
    
    597
    +      if (image->format != FT_GLYPH_FORMAT_BITMAP)
    
    598
    +      {
    
    599
    +        if (vertical_)
    
    600
    +          error = FT_Glyph_Transform(image, NULL, &ctx.vvector);
    
    601
    +
    
    602
    +        if (!error)
    
    603
    +        {
    
    604
    +          if (matrixEnabled_)
    
    605
    +            error = FT_Glyph_Transform(image, &matrix_, NULL);
    
    606
    +        }
    
    607
    +
    
    608
    +        if (error)
    
    609
    +        {
    
    610
    +          FT_Done_Glyph(image);
    
    611
    +          continue;
    
    612
    +        }
    
    613
    +      }
    
    614
    +      else
    
    615
    +      {
    
    616
    +        auto bitmap = reinterpret_cast<FT_BitmapGlyph>(image);
    
    617
    +
    
    618
    +         if (vertical_)
    
    619
    +        {
    
    620
    +           bitmap->left += static_cast<int>(ctx.vvector.x) >> 6;
    
    621
    +           bitmap->top += static_cast<int>(ctx.vvector.y) >> 6;
    
    622
    +        }
    
    623
    +      }
    
    624
    +
    
    625
    +      if (matrixEnabled_)
    
    626
    +        FT_Vector_Transform(&advance, &matrix_);
    
    627
    +
    
    628
    +      FT_Glyph_Get_CBox(image, FT_GLYPH_BBOX_PIXELS, &bbox);
    
    629
    +
    
    630
    +      // Don't check for bounding box here.
    
    631
    +      FT_Vector penPos = { (pen.x >> 6), height - (pen.y >> 6) };
    
    632
    +      renderCallback_(image, penPos, ctx);
    
    633
    +
    
    634
    +      FT_Done_Glyph(image);
    
    635
    +    }
    
    636
    +    
    
    637
    +    pen.x += advance.x;
    
    638
    +    pen.y += advance.y;
    
    639
    +
    
    640
    +    if (!advance.x && !usingString_) // add placeholder
    
    641
    +      pen.x += nonSpacingPlaceholder << 6;
    
    642
    +  }
    
    643
    +
    
    644
    +  return offset + totalCount;
    
    645
    +}
    
    646
    +
    
    647
    +
    
    648
    +void
    
    649
    +StringRenderer::clearActive(bool glyphOnly)
    
    650
    +{
    
    651
    +  for (auto& ctx : activeGlyphs_)
    
    652
    +  {
    
    653
    +    if (ctx.cacheNode)
    
    654
    +      FTC_Node_Unref(ctx.cacheNode, engine_->cacheManager());
    
    655
    +    else if (ctx.glyph)
    
    656
    +      FT_Done_Glyph(ctx.glyph); // when caching isn't used
    
    657
    +    ctx.cacheNode = NULL;
    
    658
    +    ctx.glyph = NULL;
    
    659
    +  }
    
    660
    +  if (!glyphOnly)
    
    661
    +    activeGlyphs_.clear();
    
    662
    +
    
    663
    +  glyphCacheValid_ = false;
    
    664
    +}
    
    665
    +
    
    666
    +
    
    667
    +int
    
    668
    +StringRenderer::convertCharEncoding(int charUcs4, FT_Encoding encoding)
    
    669
    +{
    
    670
    +  switch (encoding)
    
    671
    +  {
    
    672
    +  case FT_ENCODING_MS_SYMBOL:
    
    673
    +  case FT_ENCODING_UNICODE:
    
    674
    +  case FT_ENCODING_ADOBE_STANDARD: // These may be problematic...
    
    675
    +  case FT_ENCODING_ADOBE_EXPERT:
    
    676
    +  case FT_ENCODING_ADOBE_CUSTOM:
    
    677
    +  case FT_ENCODING_ADOBE_LATIN_1:
    
    678
    +    return charUcs4;
    
    679
    +  }
    
    680
    +
    
    681
    +  auto mib = -1;
    
    682
    +  switch (encoding)
    
    683
    +  {
    
    684
    +  case FT_ENCODING_SJIS:
    
    685
    +    mib = 17; // Shift_JIS
    
    686
    +    break;
    
    687
    +  case FT_ENCODING_PRC:
    
    688
    +    mib = 114; // GB 18030
    
    689
    +    break;
    
    690
    +  case FT_ENCODING_BIG5:
    
    691
    +    mib = 2026; // Big5
    
    692
    +    break;
    
    693
    +  case FT_ENCODING_WANSUNG:
    
    694
    +    mib = -949; // KS C 5601:1987, this is a fake mib value
    
    695
    +    break;
    
    696
    +  case FT_ENCODING_JOHAB:
    
    697
    +    mib = 38; //  KS C 5601:1992 / EUC-KR
    
    698
    +    break;
    
    699
    +  case FT_ENCODING_APPLE_ROMAN:
    
    700
    +    mib = 2027;
    
    701
    +    break;
    
    702
    +  }
    
    703
    +
    
    704
    +  if (mib == -1)
    
    705
    +    return charUcs4; // unsupported charmap
    
    706
    +  auto codec = QTextCodec::codecForMib(mib);
    
    707
    +  if (!codec)
    
    708
    +    return charUcs4; // unsupported
    
    709
    +
    
    710
    +  auto res = codec->fromUnicode(
    
    711
    +      QString::fromUcs4(reinterpret_cast<uint*>(&charUcs4), 1));
    
    712
    +  if (res.size() == 0)
    
    713
    +    return charUcs4;
    
    714
    +  if (res.size() == 1)
    
    715
    +    return res[0];
    
    716
    +  return ((static_cast<int>(res[0]) & 0xFF) << 8)
    
    717
    +         | (static_cast<int>(res[1]) & 0xFF);
    
    718
    +}
    
    719
    +
    
    720
    +
    
    721
    +// end of stringrenderer.cpp

  • src/ftinspect/engine/stringrenderer.hpp
    1
    +// stringrenderer.hpp
    
    2
    +
    
    3
    +// Copyright (C) 2022 by Charlie Jiang.
    
    4
    +
    
    5
    +#pragma once
    
    6
    +
    
    7
    +#include <vector>
    
    8
    +#include <functional>
    
    9
    +
    
    10
    +#include <QString>
    
    11
    +
    
    12
    +#include <ft2build.h>
    
    13
    +#include <qslider.h>
    
    14
    +#include <freetype/freetype.h>
    
    15
    +#include <freetype/ftcache.h>
    
    16
    +#include <freetype/ftglyph.h>
    
    17
    +
    
    18
    +// adopted from `ftcommon.h`
    
    19
    +
    
    20
    +class Engine;
    
    21
    +struct GlyphContext
    
    22
    +{
    
    23
    +  int charCode = 0;
    
    24
    +  int charCodeUcs4 = 0;
    
    25
    +  int glyphIndex = 0;
    
    26
    +  FT_Glyph glyph = NULL;
    
    27
    +  FTC_Node cacheNode = NULL;
    
    28
    +
    
    29
    +  FT_Pos lsbDelta = 0;    // delta caused by hinting
    
    30
    +  FT_Pos rsbDelta = 0;   // delta caused by hinting
    
    31
    +  FT_Vector hadvance = { 0, 0 }; // kerned horizontal advance
    
    32
    +
    
    33
    +  FT_Vector vvector = { 0, 0 };  // vert. origin => hori. origin
    
    34
    +  FT_Vector vadvance = { 0, 0 }; // vertical advance
    
    35
    +};
    
    36
    +
    
    37
    +// Class to populate chars to render, to load and properly position glyphs.
    
    38
    +// Use callbacks to receive characters and lines. You should save the result
    
    39
    +// from the callbacks to a cache.
    
    40
    +class StringRenderer
    
    41
    +{
    
    42
    +public:
    
    43
    +  StringRenderer(Engine* engine);
    
    44
    +  ~StringRenderer();
    
    45
    +
    
    46
    +  enum KerningDegree // XXX: Not honored actually
    
    47
    +  {
    
    48
    +    KD_None = 0,
    
    49
    +    KD_Light,
    
    50
    +    KD_Medium,
    
    51
    +    KD_Tight
    
    52
    +  };
    
    53
    +
    
    54
    +  enum KerningMode
    
    55
    +  {
    
    56
    +    KM_None = 0,
    
    57
    +    KM_Normal,
    
    58
    +    KM_Smart
    
    59
    +  };
    
    60
    +
    
    61
    +  /*
    
    62
    +   * Called when outputting a glyph. The receiver is reponsible for rendering
    
    63
    +   * the glyph to bitmap.
    
    64
    +   *
    
    65
    +   * Need to pass the pen position because sometimes the outline vector
    
    66
    +   * contains no points, and thus can't be translated to the desired pen
    
    67
    +   * position.
    
    68
    +   */
    
    69
    +  using RenderCallback = std::function<void(FT_Glyph,        // glyph
    
    70
    +                                            FT_Vector,       // penPos
    
    71
    +                                            GlyphContext&)>;
    
    72
    +  /*
    
    73
    +   * Called when outputtng a glyph with bitmap pre-rendered.
    
    74
    +   * The receiver can simply use the bitmap, mainly for color layered fonts.
    
    75
    +   * 
    
    76
    +   * TODO: Remove `RenderCallback` and do QImage creation in this class?
    
    77
    +   * The receiver is responsible for deleteing the `QImage`
    
    78
    +   * (ownership transfered).
    
    79
    +   */
    
    80
    +  using RenderImageCallback = std::function<void(QImage*,   // bitmap
    
    81
    +                                                 QRect,     // bbox
    
    82
    +                                                 FT_Vector, // penPos
    
    83
    +                                                 FT_Vector, // advance
    
    84
    +                                                 GlyphContext&)>;
    
    85
    +  /*
    
    86
    +   * Called right after the glyph is obtained from the font, before any other
    
    87
    +   * operation is done. The receiver can do pre-processing like slanting and
    
    88
    +   * emboldening in this function.
    
    89
    +   *
    
    90
    +   * The glyph pointer may be replaced. In that case, ownership is transfered
    
    91
    +   * to the renderer, and the new glyph will be eventually freed by
    
    92
    +   * the renderer. The callback is responsible to free the old glyph.
    
    93
    +   * This allows you to do the following:
    
    94
    +   * void callback(FT_Glyph* ptr) {
    
    95
    +   *     ....
    
    96
    +   *     auto oldPtr = *ptr;
    
    97
    +   *     *ptr = ....;
    
    98
    +   *     FT_Done_Glyph(olPtr);
    
    99
    +   * }
    
    100
    +   */
    
    101
    +  using PreprocessCallback = std::function<void(FT_Glyph*)>;
    
    102
    +  /*
    
    103
    +   * Called when a new line begins.
    
    104
    +   */
    
    105
    +  using LineBeginCallback = std::function<void(FT_Vector, // initial penPos
    
    106
    +                                               double)>;  // size (points)
    
    107
    +
    
    108
    +  //////// Getters
    
    109
    +  bool isWaterfall() { return waterfall_; }
    
    110
    +  double position(){ return position_; }
    
    111
    +  int charMapIndex() { return charMapIndex_; }
    
    112
    +
    
    113
    +  //////// Callbacks
    
    114
    +  void
    
    115
    +  setCallback(RenderCallback cb)
    
    116
    +  {
    
    117
    +    renderCallback_ = std::move(cb);
    
    118
    +  }
    
    119
    +  void
    
    120
    +  setImageCallback(RenderImageCallback cb)
    
    121
    +  {
    
    122
    +    renderImageCallback_ = std::move(cb);
    
    123
    +  }
    
    124
    +  void
    
    125
    +  setPreprocessCallback(PreprocessCallback cb)
    
    126
    +  {
    
    127
    +    glyphPreprocessCallback_ = std::move(cb);
    
    128
    +  }
    
    129
    +  void
    
    130
    +  setLineBeginCallback(LineBeginCallback cb)
    
    131
    +  {
    
    132
    +    lineBeginCallback_ = std::move(cb);
    
    133
    +  }
    
    134
    +
    
    135
    +  //////// Setters for options
    
    136
    +  void setCharMapIndex(int charMapIndex, int limitIndex);
    
    137
    +  void setRepeated(bool repeated) { repeated_ = repeated; }
    
    138
    +  void setVertical(bool vertical) { vertical_ = vertical; }
    
    139
    +  void setRotation(double rotation);
    
    140
    +  void setWaterfall(bool waterfall) { waterfall_ = waterfall; }
    
    141
    +  void setWaterfallParameters(double start, double end)
    
    142
    +  {
    
    143
    +    waterfallStart_ = start;
    
    144
    +    waterfallEnd_ = end;
    
    145
    +  }
    
    146
    +  void setPosition(double pos) { position_ = pos; }
    
    147
    +  void setLsbRsbDelta(bool enabled) { lsbRsbDeltaEnabled_ = enabled; }
    
    148
    +  void setKerning(bool kerning);
    
    149
    +
    
    150
    +  // Need to be called when font or charMap changes
    
    151
    +  void setUseString(QString const& string);
    
    152
    +  void setUseAllGlyphs();
    
    153
    +
    
    154
    +  //////// Actions
    
    155
    +  int render(int width,
    
    156
    +             int height,
    
    157
    +             int offset);
    
    158
    +  int renderLine(int x, int y,
    
    159
    +                 int width, int height,
    
    160
    +                 int offset,
    
    161
    +                 bool handleMultiLine = false);
    
    162
    +
    
    163
    +  void reloadAll();      // text/font/charmap changes, will call
    
    164
    +                         // `reloadGlyphs`
    
    165
    +  void reloadGlyphs();   // any other parameter changes
    
    166
    +
    
    167
    +private:
    
    168
    +  Engine* engine_;
    
    169
    +
    
    170
    +  // Generally, rendering has those steps:
    
    171
    +  // 1. If in string mode, the string is load into `activeGlyphs_`
    
    172
    +  //    (in `updateString`)
    
    173
    +  // 2. The char codes in contexts are converted to glyph indices
    
    174
    +  //    (in `reloadGlyphIndices`)
    
    175
    +  // 3. If in string mode, glyphs are loaded into contexts.
    
    176
    +  //    (in `loadStringGlyphs`)
    
    177
    +  // 4. In `render` function, according to mode, `renderLine` is called line
    
    178
    +  //    by line (as well as `prepareRendering`).
    
    179
    +  // 5. In `renderLine`, if in all glyphs mode, glyphs from the begin index
    
    180
    +  //    are loaded until the line is full (if the glyph already exists, it will
    
    181
    +  //    be reused). If in string mode, it will directly use the prepared glyphs.
    
    182
    +  //    Preprocessing is done within this step, such as emboldening or stroking.
    
    183
    +  //    Eventually the `FT_Glyph` pointer is passed to the callback.
    
    184
    +  
    
    185
    +  GlyphContext tempGlyphContext_;
    
    186
    +  // This vector stores all active glyphs for rendering. When rendering strings,
    
    187
    +  // this is the container for chars, so DO NOT directly clear it to flush
    
    188
    +  // cache, you should clean glyph objects only. However when rendering all
    
    189
    +  // glyphs, it's generally to directly wipe the vector because it's dynamically
    
    190
    +  // generated in `render` function (see above).
    
    191
    +  //
    
    192
    +  // Note: Because of kerning, this list must be ordered and allow duplicate
    
    193
    +  //       characters.
    
    194
    +  //
    
    195
    +  // Actually this means 3 parts of storage: string charcode, glyph indices and
    
    196
    +  // glyph (+ all related info). Different parameter changes will trigger
    
    197
    +  // different levels of flushing.
    
    198
    +  std::vector<GlyphContext> activeGlyphs_;
    
    199
    +  bool glyphCacheValid_ = false;
    
    200
    +
    
    201
    +  int charMapIndex_ = 0;
    
    202
    +  int limitIndex_ = 0;
    
    203
    +  bool usingString_ = false;
    
    204
    +  bool repeated_ = false;
    
    205
    +  bool vertical_ = false;
    
    206
    +  double position_ = 0.5;
    
    207
    +  double rotation_ = 0;
    
    208
    +  int kerningDegree_ = KD_None;
    
    209
    +  KerningMode kerningMode_ = KM_None;
    
    210
    +  FT_Pos trackingKerning_ = 0;
    
    211
    +  FT_Matrix matrix_ = {};
    
    212
    +  bool matrixEnabled_ = false;
    
    213
    +  bool lsbRsbDeltaEnabled_ = true;
    
    214
    +
    
    215
    +  bool waterfall_ = false;
    
    216
    +  double waterfallStart_ = -1;
    
    217
    +  double waterfallEnd_ = -1; // -1 = Auto
    
    218
    +
    
    219
    +  RenderCallback renderCallback_;
    
    220
    +  RenderImageCallback renderImageCallback_;
    
    221
    +  PreprocessCallback glyphPreprocessCallback_;
    
    222
    +  LineBeginCallback lineBeginCallback_;
    
    223
    +
    
    224
    +  void reloadGlyphIndices(); // for string rendering
    
    225
    +  void prepareRendering();
    
    226
    +  void loadSingleContext(GlyphContext* ctx, GlyphContext* prev);
    
    227
    +  // Need to be called when font, charMap or size changes;
    
    228
    +  void loadStringGlyphs();
    
    229
    +  // Returns total line count
    
    230
    +  int prepareLine(int offset, 
    
    231
    +                  int lineWidth,
    
    232
    +                  FT_Vector& outActualLineWidth,
    
    233
    +                  int nonSpacingPlaceholder,
    
    234
    +                  bool handleMultiLine = false);
    
    235
    +  void clearActive(bool glyphOnly = false);
    
    236
    +
    
    237
    +  int convertCharEncoding(int charUcs4, FT_Encoding encoding);
    
    238
    +};
    
    239
    +
    
    240
    +
    
    241
    +// end of stringrenderer.hpp

  • src/ftinspect/glyphcomponents/glyphcontinuous.cpp
    1
    +// glyphcontinuous.cpp
    
    2
    +
    
    3
    +// Copyright (C) 2022 by Charlie Jiang.
    
    4
    +
    
    5
    +#include "glyphcontinuous.hpp"
    
    6
    +
    
    7
    +#include "../engine/engine.hpp"
    
    8
    +
    
    9
    +#include <QPainter>
    
    10
    +#include <QWheelEvent>
    
    11
    +
    
    12
    +#include <freetype/ftbitmap.h>
    
    13
    +
    
    14
    +
    
    15
    +GlyphCacheEntry::~GlyphCacheEntry()
    
    16
    +{
    
    17
    +  delete image;
    
    18
    +}
    
    19
    +
    
    20
    +
    
    21
    +GlyphCacheEntry::GlyphCacheEntry(GlyphCacheEntry&& other) noexcept
    
    22
    +{
    
    23
    +  *this = std::move(other);
    
    24
    +}
    
    25
    +
    
    26
    +
    
    27
    +GlyphCacheEntry&
    
    28
    +GlyphCacheEntry::operator=(GlyphCacheEntry&& other) noexcept
    
    29
    +{
    
    30
    +  if (this == &other)
    
    31
    +    return *this;
    
    32
    +
    
    33
    +  auto oldImage = image;
    
    34
    +  image = other.image;
    
    35
    +  basePosition = other.basePosition;
    
    36
    +  penPos = other.penPos;
    
    37
    +  charCode = other.charCode;
    
    38
    +  glyphIndex = other.glyphIndex;
    
    39
    +  nonSpacingPlaceholder = other.nonSpacingPlaceholder;
    
    40
    +  advance = other.advance;
    
    41
    +  other.image = oldImage;
    
    42
    +  return *this;
    
    43
    +}
    
    44
    +
    
    45
    +
    
    46
    +GlyphContinuous::GlyphContinuous(QWidget* parent, Engine* engine)
    
    47
    +: QWidget(parent),
    
    48
    +  engine_(engine),
    
    49
    +  stringRenderer_(engine)
    
    50
    +{
    
    51
    +  setAcceptDrops(false);
    
    52
    +  setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    
    53
    +
    
    54
    +  flashTimer_ = new QTimer(this);
    
    55
    +  flashTimer_->setInterval(FlashIntervalMs);
    
    56
    +  connect(flashTimer_, &QTimer::timeout,
    
    57
    +          this, &GlyphContinuous::flashTimerFired);
    
    58
    +
    
    59
    +  FT_Stroker_New(engine_->ftLibrary(), &stroker_);
    
    60
    +}
    
    61
    +
    
    62
    +
    
    63
    +GlyphContinuous::~GlyphContinuous()
    
    64
    +{
    
    65
    +  FT_Stroker_Done(stroker_);
    
    66
    +}
    
    67
    +
    
    68
    +
    
    69
    +void
    
    70
    +GlyphContinuous::setSource(Source source)
    
    71
    +{
    
    72
    +  source_ = source;
    
    73
    +  switch (source)
    
    74
    +  {
    
    75
    +  case SRC_AllGlyphs:
    
    76
    +    stringRenderer_.setUseAllGlyphs();
    
    77
    +    positionDelta_ = {};
    
    78
    +    break;
    
    79
    +
    
    80
    +  case SRC_TextStringRepeated:
    
    81
    +    positionDelta_ = {};
    
    82
    +    /* fall through */
    
    83
    +  case SRC_TextString:
    
    84
    +    updateRendererText();
    
    85
    +    break;
    
    86
    +  }
    
    87
    +}
    
    88
    +
    
    89
    +
    
    90
    +void
    
    91
    +GlyphContinuous::setSourceText(QString text)
    
    92
    +{
    
    93
    +  text_ = std::move(text);
    
    94
    +  updateRendererText();
    
    95
    +}
    
    96
    +
    
    97
    +
    
    98
    +void
    
    99
    +GlyphContinuous::flashOnGlyph(int glyphIndex)
    
    100
    +{
    
    101
    +  flashTimer_->stop();
    
    102
    +
    
    103
    +  flashGlyphIndex_ = glyphIndex;
    
    104
    +  flashRemainingCount_ = FlashDurationMs / FlashIntervalMs;
    
    105
    +  flashTimer_->start();
    
    106
    +}
    
    107
    +
    
    108
    +
    
    109
    +void
    
    110
    +GlyphContinuous::stopFlashing()
    
    111
    +{
    
    112
    +  flashGlyphIndex_ = -1;
    
    113
    +  flashTimer_->stop();
    
    114
    +}
    
    115
    +
    
    116
    +
    
    117
    +void
    
    118
    +GlyphContinuous::purgeCache()
    
    119
    +{
    
    120
    +  glyphCache_.clear();
    
    121
    +  backgroundColorCache_ = engine_->renderingEngine()->background();
    
    122
    +  currentWritingLine_ = NULL;
    
    123
    +}
    
    124
    +
    
    125
    +
    
    126
    +void
    
    127
    +GlyphContinuous::resetPositionDelta()
    
    128
    +{
    
    129
    +  positionDelta_ = {};
    
    130
    +  repaint();
    
    131
    +}
    
    132
    +
    
    133
    +
    
    134
    +void
    
    135
    +GlyphContinuous::paintEvent(QPaintEvent* event)
    
    136
    +{
    
    137
    +  QPainter painter(this);
    
    138
    +  painter.fillRect(rect(), backgroundColorCache_);
    
    139
    +  painter.scale(scale_, scale_);
    
    140
    +
    
    141
    +  if (glyphCache_.empty())
    
    142
    +    fillCache();
    
    143
    +  paintCache(&painter);
    
    144
    +}
    
    145
    +
    
    146
    +
    
    147
    +void
    
    148
    +GlyphContinuous::wheelEvent(QWheelEvent* event)
    
    149
    +{
    
    150
    +  int numSteps = event->angleDelta().y() / 120;
    
    151
    +  if (event->modifiers() & Qt::ShiftModifier)
    
    152
    +    emit wheelResize(numSteps);
    
    153
    +  else if (event->modifiers() & Qt::ControlModifier)
    
    154
    +    emit wheelZoom(numSteps);
    
    155
    +  else if (event->modifiers() == 0)
    
    156
    +    emit wheelNavigate(-numSteps);
    
    157
    +}
    
    158
    +
    
    159
    +
    
    160
    +void
    
    161
    +GlyphContinuous::resizeEvent(QResizeEvent* event)
    
    162
    +{
    
    163
    +  purgeCache();
    
    164
    +  QWidget::resizeEvent(event);
    
    165
    +}
    
    166
    +
    
    167
    +
    
    168
    +void
    
    169
    +GlyphContinuous::mousePressEvent(QMouseEvent* event)
    
    170
    +{
    
    171
    +  if (!mouseOperationEnabled_)
    
    172
    +    return;
    
    173
    +  if (event->button() == Qt::LeftButton)
    
    174
    +  {
    
    175
    +    prevPositionDelta_ = positionDelta_;
    
    176
    +    mouseDownPostition_ = event->pos();
    
    177
    +    prevHoriPosition_ = stringRenderer_.position();
    
    178
    +    prevIndex_ = beginIndex_;
    
    179
    +    // We need to precalculate this value because after the first change of
    
    180
    +    // the begin index, the average line count would change. If we don't use the
    
    181
    +    // old value, then moving up/down for the same distance would not return
    
    182
    +    // to the original index which is confusing.
    
    183
    +    averageLineCount_ = calculateAverageLineCount();
    
    184
    +  }
    
    185
    +}
    
    186
    +
    
    187
    +
    
    188
    +void
    
    189
    +GlyphContinuous::mouseMoveEvent(QMouseEvent* event)
    
    190
    +{
    
    191
    +  if (!mouseOperationEnabled_)
    
    192
    +    return;
    
    193
    +  if (event->buttons() != Qt::LeftButton)
    
    194
    +    return;
    
    195
    +  auto delta = event->pos() - mouseDownPostition_;
    
    196
    +  delta /= scale_;
    
    197
    +  if (source_ == SRC_AllGlyphs)
    
    198
    +  {
    
    199
    +    auto deltaIndex = -delta.x() / HorizontalUnitLength
    
    200
    +                          - delta.y() / VerticalUnitLength * averageLineCount_;
    
    201
    +    if (prevIndex_ + deltaIndex != beginIndex_)
    
    202
    +      emit beginIndexChangeRequest(beginIndex_ + deltaIndex);
    
    203
    +  }
    
    204
    +  else if (source_ == SRC_TextString)
    
    205
    +  {
    
    206
    +    positionDelta_ = prevPositionDelta_ + delta;
    
    207
    +    positionDelta_.setX(0); // Don't move horizontally
    
    208
    +
    
    209
    +    // but use the renderer
    
    210
    +    // purpose for two scale_: one for undoing the `delta /= scale_`
    
    211
    +    // the other for effectively dividing width by the scale
    
    212
    +    auto horiPos = delta.x() * scale_ * scale_ / static_cast<double>(width());
    
    213
    +    horiPos += prevHoriPosition_;
    
    214
    +    horiPos = qBound(0.0, horiPos, 1.0);
    
    215
    +    stringRenderer_.setPosition(horiPos);
    
    216
    +
    
    217
    +    purgeCache();
    
    218
    +    repaint();
    
    219
    +  }
    
    220
    +}
    
    221
    +
    
    222
    +
    
    223
    +void
    
    224
    +GlyphContinuous::paintByRenderer()
    
    225
    +{
    
    226
    +  purgeCache();
    
    227
    +
    
    228
    +  stringRenderer_.setRepeated(source_ == SRC_TextStringRepeated);
    
    229
    +  stringRenderer_.setCallback(
    
    230
    +    [&](FT_Glyph glyph, FT_Vector penPos, GlyphContext& ctx)
    
    231
    +    {
    
    232
    +      saveSingleGlyph(glyph, penPos, ctx);
    
    233
    +    });
    
    234
    +  stringRenderer_.setImageCallback(
    
    235
    +    [&](QImage* image,
    
    236
    +        QRect pos, 
    
    237
    +        FT_Vector penPos, FT_Vector advance,
    
    238
    +        GlyphContext& ctx)
    
    239
    +    {
    
    240
    +      saveSingleGlyphImage(image, pos, penPos, advance, ctx);
    
    241
    +    });
    
    242
    +  stringRenderer_.setPreprocessCallback(
    
    243
    +    [&](FT_Glyph* ptr)
    
    244
    +    {
    
    245
    +      preprocessGlyph(ptr);
    
    246
    +    });
    
    247
    +  stringRenderer_.setLineBeginCallback(
    
    248
    +    [&](FT_Vector pos, double size)
    
    249
    +    {
    
    250
    +      beginSaveLine(pos, size);
    
    251
    +    });
    
    252
    +  auto count = stringRenderer_.render(static_cast<int>(width() / scale_), 
    
    253
    +                                      static_cast<int>(height() / scale_),
    
    254
    +                                      beginIndex_);
    
    255
    +  if (source_ == SRC_AllGlyphs)
    
    256
    +    displayingCount_ = count;
    
    257
    +  else
    
    258
    +    displayingCount_ = 0;
    
    259
    +}
    
    260
    +
    
    261
    +
    
    262
    +void
    
    263
    +GlyphContinuous::transformGlyphFancy(FT_Glyph glyph)
    
    264
    +{
    
    265
    +  auto& metrics = engine_->currentFontMetrics();
    
    266
    +  auto emboldeningX = (FT_Pos)(metrics.y_ppem * 64 * boldX_);
    
    267
    +  auto emboldeningY = (FT_Pos)(metrics.y_ppem * 64 * boldY_);
    
    268
    +  // adopted from ftview.c:289
    
    269
    +  if (glyph->format == FT_GLYPH_FORMAT_OUTLINE)
    
    270
    +  {
    
    271
    +    auto outline = reinterpret_cast<FT_OutlineGlyph>(glyph)->outline;
    
    272
    +    FT_Glyph_Transform(glyph, &shearMatrix_, NULL);
    
    273
    +    if (FT_Outline_EmboldenXY(&outline, emboldeningX, emboldeningY))
    
    274
    +    {
    
    275
    +      // XXX error handling?
    
    276
    +      return;
    
    277
    +    }
    
    278
    +
    
    279
    +    if (glyph->advance.x)
    
    280
    +      glyph->advance.x += emboldeningX;
    
    281
    +
    
    282
    +    if (glyph->advance.y)
    
    283
    +      glyph->advance.y += emboldeningY;
    
    284
    +  }
    
    285
    +  else if (glyph->format == FT_GLYPH_FORMAT_BITMAP)
    
    286
    +  {
    
    287
    +    auto xstr = emboldeningX & ~63;
    
    288
    +    auto ystr = emboldeningY & ~63;
    
    289
    +
    
    290
    +    auto bitmap = &reinterpret_cast<FT_BitmapGlyph>(glyph)->bitmap;
    
    291
    +    // No shearing support for bitmap
    
    292
    +    FT_Bitmap_Embolden(engine_->ftLibrary(), bitmap, 
    
    293
    +                       xstr, ystr);
    
    294
    +  }
    
    295
    +  else
    
    296
    +    return; // XXX no support for SVG
    
    297
    +}
    
    298
    +
    
    299
    +
    
    300
    +FT_Glyph
    
    301
    +GlyphContinuous::transformGlyphStroked(FT_Glyph glyph)
    
    302
    +{
    
    303
    +  // Well, here only outline glyph is supported.
    
    304
    +  if (glyph->format != FT_GLYPH_FORMAT_OUTLINE)
    
    305
    +    return NULL;
    
    306
    +  auto error = FT_Glyph_Stroke(&glyph, stroker_, 0);
    
    307
    +  if (error)
    
    308
    +    return NULL;
    
    309
    +  return glyph;
    
    310
    +}
    
    311
    +
    
    312
    +
    
    313
    +void
    
    314
    +GlyphContinuous::paintCache(QPainter* painter)
    
    315
    +{
    
    316
    +  bool flashFlipFlop = false;
    
    317
    +  if (flashRemainingCount_ >= 0)
    
    318
    +  {
    
    319
    +    if (flashGlyphIndex_ >= 0) // only flash when the glyph index valid
    
    320
    +      flashFlipFlop = flashRemainingCount_ % 2 == 1;
    
    321
    +    else
    
    322
    +    {
    
    323
    +      flashTimer_->stop();
    
    324
    +      flashRemainingCount_ = 0;
    
    325
    +    }
    
    326
    +    flashRemainingCount_--;
    
    327
    +  }
    
    328
    +  else if (flashGlyphIndex_ >= 0)
    
    329
    +  {
    
    330
    +    flashGlyphIndex_ = -1;
    
    331
    +    flashTimer_->stop();
    
    332
    +  }
    
    333
    +
    
    334
    +  if (stringRenderer_.isWaterfall())
    
    335
    +    positionDelta_.setY(0);
    
    336
    +  for (auto& line : glyphCache_)
    
    337
    +  {
    
    338
    +    beginDrawCacheLine(painter, line);
    
    339
    +    for (auto& glyph : line.entries)
    
    340
    +    {
    
    341
    +      if (glyph.glyphIndex == flashGlyphIndex_ && flashFlipFlop)
    
    342
    +        drawCacheGlyph(painter, glyph, true);
    
    343
    +      else
    
    344
    +        drawCacheGlyph(painter, glyph);
    
    345
    +    }
    
    346
    +  }
    
    347
    +}
    
    348
    +
    
    349
    +
    
    350
    +void
    
    351
    +GlyphContinuous::fillCache()
    
    352
    +{
    
    353
    +  prePaint();
    
    354
    +  paintByRenderer();
    
    355
    +  emit displayingCountUpdated(displayingCount_);
    
    356
    +}
    
    357
    +
    
    358
    +
    
    359
    +void
    
    360
    +GlyphContinuous::prePaint()
    
    361
    +{
    
    362
    +  displayingCount_ = 0;
    
    363
    +
    
    364
    +  // Used by fancy:
    
    365
    +  // adopted from ftview.c:289
    
    366
    +  /***************************************************************/
    
    367
    +  /*                                                             */
    
    368
    +  /*  2*2 affine transformation matrix, 16.16 fixed float format */
    
    369
    +  /*                                                             */
    
    370
    +  /*  Shear matrix:                                              */
    
    371
    +  /*                                                             */
    
    372
    +  /*         | x' |     | 1  k |   | x |          x' = x + ky    */
    
    373
    +  /*         |    |  =  |      | * |   |   <==>                  */
    
    374
    +  /*         | y' |     | 0  1 |   | y |          y' = y         */
    
    375
    +  /*                                                             */
    
    376
    +  /*        outline'     shear    outline                        */
    
    377
    +  /*                                                             */
    
    378
    +  /***************************************************************/
    
    379
    +  
    
    380
    +
    
    381
    +  shearMatrix_.xx = 1 << 16;
    
    382
    +  shearMatrix_.xy = static_cast<FT_Fixed>(slant_ * (1 << 16));
    
    383
    +  shearMatrix_.yx = 0;
    
    384
    +  shearMatrix_.yy = 1 << 16;
    
    385
    +}
    
    386
    +
    
    387
    +
    
    388
    +void
    
    389
    +GlyphContinuous::updateStroke()
    
    390
    +{
    
    391
    +  if (mode_ != M_Stroked || !engine_->renderReady())
    
    392
    +    return;
    
    393
    +
    
    394
    +  auto& metrics = engine_->currentFontMetrics();
    
    395
    +  auto radius = static_cast<FT_Fixed>(metrics.y_ppem * 64 * strokeRadius_);
    
    396
    +  strokeRadiusForSize_ = radius;
    
    397
    +  FT_Stroker_Set(stroker_, radius,
    
    398
    +                 FT_STROKER_LINECAP_ROUND,
    
    399
    +                 FT_STROKER_LINEJOIN_ROUND,
    
    400
    +                 0);
    
    401
    +}
    
    402
    +
    
    403
    +
    
    404
    +void
    
    405
    +GlyphContinuous::updateRendererText()
    
    406
    +{
    
    407
    +  stringRenderer_.setUseString(text_); // TODO this need to be called when font,
    
    408
    +                                       // size or charmap change
    
    409
    +}
    
    410
    +
    
    411
    +
    
    412
    +void
    
    413
    +GlyphContinuous::preprocessGlyph(FT_Glyph* glyphPtr)
    
    414
    +{
    
    415
    +  auto glyph = *glyphPtr;
    
    416
    +  switch (mode_)
    
    417
    +  {
    
    418
    +  case M_Fancy:
    
    419
    +    transformGlyphFancy(glyph);
    
    420
    +    break;
    
    421
    +  case M_Stroked:
    
    422
    +  {
    
    423
    +    auto stroked = transformGlyphStroked(glyph);
    
    424
    +    if (stroked)
    
    425
    +    {
    
    426
    +      FT_Done_Glyph(glyph);
    
    427
    +      *glyphPtr = stroked;
    
    428
    +    }
    
    429
    +  }
    
    430
    +  break;
    
    431
    +  default:; // Nothing for M_NORMAL.
    
    432
    +  }
    
    433
    +}
    
    434
    +
    
    435
    +
    
    436
    +void
    
    437
    +GlyphContinuous::beginSaveLine(FT_Vector pos,
    
    438
    +                               double sizePoint)
    
    439
    +{
    
    440
    +  glyphCache_.emplace_back();
    
    441
    +  currentWritingLine_ = &glyphCache_.back();
    
    442
    +  currentWritingLine_->nonSpacingPlaceholder
    
    443
    +    = engine_->currentFontMetrics().y_ppem / 2;
    
    444
    +  currentWritingLine_->sizePoint = sizePoint;
    
    445
    +  currentWritingLine_->basePosition = { static_cast<int>(pos.x),
    
    446
    +                                        static_cast<int>(pos.y) };
    
    447
    +}
    
    448
    +
    
    449
    +
    
    450
    +void
    
    451
    +GlyphContinuous::saveSingleGlyph(FT_Glyph glyph,
    
    452
    +                                 FT_Vector penPos,
    
    453
    +                                 GlyphContext gctx)
    
    454
    +{
    
    455
    +  if (!currentWritingLine_)
    
    456
    +    return;
    
    457
    +
    
    458
    +  QRect rect;
    
    459
    +  QImage* image = engine_->renderingEngine()->convertGlyphToQImage(glyph, 
    
    460
    +                                                                   &rect, 
    
    461
    +                                                                   true);
    
    462
    +  saveSingleGlyphImage(image, rect, penPos, glyph->advance, gctx);
    
    463
    +}
    
    464
    +
    
    465
    +
    
    466
    +void
    
    467
    +GlyphContinuous::saveSingleGlyphImage(QImage* image,
    
    468
    +                                      QRect rect,
    
    469
    +                                      FT_Vector penPos,
    
    470
    +                                      FT_Vector advance,
    
    471
    +                                      GlyphContext gctx)
    
    472
    +{
    
    473
    +  if (!currentWritingLine_)
    
    474
    +    return;
    
    475
    +
    
    476
    +  currentWritingLine_->entries.emplace_back();
    
    477
    +  auto& entry = currentWritingLine_->entries.back();
    
    478
    +
    
    479
    +  QPoint penPosPoint = { static_cast<int>(penPos.x),
    
    480
    +                         static_cast<int>(penPos.y) };
    
    481
    +
    
    482
    +  rect.translate(penPosPoint);
    
    483
    +
    
    484
    +  entry.image = image;
    
    485
    +  entry.basePosition = rect;
    
    486
    +  entry.charCode = gctx.charCode;
    
    487
    +  entry.glyphIndex = gctx.glyphIndex;
    
    488
    +  entry.advance = advance;
    
    489
    +  entry.penPos = penPosPoint;
    
    490
    +  entry.nonSpacingPlaceholder = currentWritingLine_->nonSpacingPlaceholder;
    
    491
    +}
    
    492
    +
    
    493
    +
    
    494
    +void
    
    495
    +GlyphContinuous::beginDrawCacheLine(QPainter* painter,
    
    496
    +                                    GlyphCacheLine& line)
    
    497
    +{
    
    498
    +  // Now only used by waterfall mode to draw a size indicator.
    
    499
    +  if (!stringRenderer_.isWaterfall())
    
    500
    +  {
    
    501
    +    sizeIndicatorOffset_ = 0;
    
    502
    +    return;
    
    503
    +  }
    
    504
    +
    
    505
    +  auto oldFont = painter->font();
    
    506
    +  oldFont.setPointSizeF(line.sizePoint);
    
    507
    +  painter->setFont(oldFont);
    
    508
    +  auto metrics = painter->fontMetrics();
    
    509
    +
    
    510
    +  auto printSize = line.sizePoint;
    
    511
    +  if (engine_->currentFontBitmapOnly())
    
    512
    +    printSize = printSize * engine_->dpi() / 72.0; // convert back
    
    513
    +  auto sizePrefix = QString("%1: ").arg(printSize);
    
    514
    +  painter->drawText(line.basePosition, sizePrefix);
    
    515
    +
    
    516
    +  sizeIndicatorOffset_ = metrics.horizontalAdvance(sizePrefix);
    
    517
    +  line.sizeIndicatorOffset = sizeIndicatorOffset_;
    
    518
    +}
    
    519
    +
    
    520
    +
    
    521
    +void
    
    522
    +GlyphContinuous::drawCacheGlyph(QPainter* painter,
    
    523
    +                                const GlyphCacheEntry& entry,
    
    524
    +                                bool colorInverted)
    
    525
    +{
    
    526
    +  // ftview.c:557
    
    527
    +  // Well, metrics is also part of the cache...
    
    528
    +  int width = entry.advance.x ? entry.advance.x >> 16
    
    529
    +                              : entry.nonSpacingPlaceholder;
    
    530
    +
    
    531
    +  if (entry.advance.x == 0 
    
    532
    +      && !stringRenderer_.isWaterfall()
    
    533
    +      && source_ == SRC_AllGlyphs)
    
    534
    +  {
    
    535
    +    // Draw a red square to indicate non-spacing glyphs
    
    536
    +    auto squarePoint = entry.penPos;
    
    537
    +    squarePoint.setY(squarePoint.y() - width);
    
    538
    +    auto rect = QRect(squarePoint, QSize(width, width));
    
    539
    +    painter->fillRect(rect, Qt::red);
    
    540
    +  }
    
    541
    +
    
    542
    +  QRect rect = entry.basePosition;
    
    543
    +  rect.moveLeft(rect.x() + sizeIndicatorOffset_);
    
    544
    +  rect.translate(positionDelta_);
    
    545
    +
    
    546
    +  if (colorInverted)
    
    547
    +  {
    
    548
    +    auto inverted = entry.image->copy();
    
    549
    +    inverted.invertPixels();
    
    550
    +    painter->drawImage(rect.topLeft(), inverted);
    
    551
    +  }
    
    552
    +  else
    
    553
    +    painter->drawImage(rect.topLeft(), *entry.image);
    
    554
    +}
    
    555
    +
    
    556
    +
    
    557
    +GlyphCacheEntry*
    
    558
    +GlyphContinuous::findGlyphByMouse(QPoint position,
    
    559
    +                                  double* outSizePoint)
    
    560
    +{
    
    561
    +  position -= positionDelta_;
    
    562
    +  position /= scale_;
    
    563
    +  for (auto& line : glyphCache_)
    
    564
    +    for (auto& entry : line.entries)
    
    565
    +    {
    
    566
    +      auto rect = entry.basePosition;
    
    567
    +      rect.moveLeft(rect.x() + line.sizeIndicatorOffset);
    
    568
    +      if (rect.contains(position))
    
    569
    +      {
    
    570
    +        if (outSizePoint)
    
    571
    +          *outSizePoint = line.sizePoint;
    
    572
    +        return &entry;
    
    573
    +      }
    
    574
    +    }
    
    575
    +  return NULL;
    
    576
    +}
    
    577
    +
    
    578
    +
    
    579
    +int
    
    580
    +GlyphContinuous::calculateAverageLineCount()
    
    581
    +{
    
    582
    +  int averageLineCount = 0;
    
    583
    +  for (auto& line : glyphCache_)
    
    584
    +  {
    
    585
    +    // line.entries.size must < INT_MAX because the total glyph count in
    
    586
    +    // the renderer is below that
    
    587
    +    averageLineCount += static_cast<int>(line.entries.size());
    
    588
    +  }
    
    589
    +  if (!glyphCache_.empty())
    
    590
    +    averageLineCount /= static_cast<int>(glyphCache_.size());
    
    591
    +  return averageLineCount;
    
    592
    +}
    
    593
    +
    
    594
    +
    
    595
    +void
    
    596
    +GlyphContinuous::flashTimerFired()
    
    597
    +{
    
    598
    +  repaint();
    
    599
    +}
    
    600
    +
    
    601
    +
    
    602
    +// end of glyphcontinuous.cpp

  • src/ftinspect/glyphcomponents/glyphcontinuous.hpp
    1
    +// glyphcontinuous.hpp
    
    2
    +
    
    3
    +// Copyright (C) 2022 by Charlie Jiang.
    
    4
    +
    
    5
    +#pragma once
    
    6
    +
    
    7
    +#include "graphicsdefault.hpp"
    
    8
    +#include "../engine/stringrenderer.hpp"
    
    9
    +
    
    10
    +#include <utility>
    
    11
    +#include <vector>
    
    12
    +
    
    13
    +#include <QWidget>
    
    14
    +#include <QImage>
    
    15
    +#include <QTimer>
    
    16
    +
    
    17
    +#include <freetype/freetype.h>
    
    18
    +#include <freetype/ftglyph.h>
    
    19
    +#include <freetype/ftoutln.h>
    
    20
    +#include <freetype/ftstroke.h>
    
    21
    +
    
    22
    +
    
    23
    +// We store images in the cache so we don't need to render all glyphs every time
    
    24
    +// when repainting the widget.
    
    25
    +struct GlyphCacheEntry
    
    26
    +{
    
    27
    +  QImage* image = NULL;
    
    28
    +  QRect basePosition = {};
    
    29
    +  QPoint penPos = {};
    
    30
    +  int charCode = -1;
    
    31
    +  int glyphIndex = -1;
    
    32
    +  unsigned nonSpacingPlaceholder = 0;
    
    33
    +
    
    34
    +  FT_Vector advance = {};
    
    35
    +
    
    36
    +  GlyphCacheEntry() {}
    
    37
    +  ~GlyphCacheEntry();
    
    38
    +  GlyphCacheEntry(const GlyphCacheEntry& other) = delete;
    
    39
    +  GlyphCacheEntry& operator=(const GlyphCacheEntry& other) = delete;
    
    40
    +  GlyphCacheEntry(GlyphCacheEntry&& other) noexcept;
    
    41
    +  GlyphCacheEntry& operator=(GlyphCacheEntry&& other) noexcept;
    
    42
    +};
    
    43
    +
    
    44
    +
    
    45
    +struct GlyphCacheLine
    
    46
    +{
    
    47
    +  QPoint basePosition = {};
    
    48
    +  double sizePoint = 0.0;
    
    49
    +  int sizeIndicatorOffset;
    
    50
    +  unsigned short nonSpacingPlaceholder;
    
    51
    +  std::vector<GlyphCacheEntry> entries;
    
    52
    +};
    
    53
    +
    
    54
    +
    
    55
    +class Engine;
    
    56
    +class GlyphContinuous
    
    57
    +: public QWidget
    
    58
    +{
    
    59
    +  Q_OBJECT
    
    60
    +public:
    
    61
    +  GlyphContinuous(QWidget* parent, Engine* engine);
    
    62
    +  ~GlyphContinuous() override;
    
    63
    +
    
    64
    +  enum Source : int
    
    65
    +  {
    
    66
    +    SRC_AllGlyphs,
    
    67
    +    SRC_TextString,
    
    68
    +    SRC_TextStringRepeated
    
    69
    +  };
    
    70
    +
    
    71
    +  enum Mode : int
    
    72
    +  {
    
    73
    +    M_Normal,
    
    74
    +    M_Fancy,
    
    75
    +    M_Stroked
    
    76
    +  };
    
    77
    +
    
    78
    +  int displayingCount() { return displayingCount_; }
    
    79
    +  StringRenderer& stringRenderer() { return stringRenderer_; }
    
    80
    +
    
    81
    +  // all those setters don't trigger repaint.
    
    82
    +  void setBeginIndex(int index) { beginIndex_ = index; }
    
    83
    +  void setSource(Source source);
    
    84
    +  void setMode(Mode mode) { mode_ = mode; }
    
    85
    +  void setScale(double scale) { scale_ = scale; }
    
    86
    +  void setFancyParams(double boldX, double boldY, double slant)
    
    87
    +  {
    
    88
    +    boldX_ = boldX;
    
    89
    +    boldY_ = boldY;
    
    90
    +    slant_ = slant;
    
    91
    +  }
    
    92
    +  void setStrokeRadius(double radius) { strokeRadius_ = radius; }
    
    93
    +  void setSourceText(QString text);
    
    94
    +  void setMouseOperationEnabled(bool enabled)
    
    95
    +  {
    
    96
    +    mouseOperationEnabled_ = enabled;
    
    97
    +  }
    
    98
    +  
    
    99
    +  void flashOnGlyph(int glyphIndex);
    
    100
    +  void stopFlashing();
    
    101
    +  void purgeCache();
    
    102
    +  void resetPositionDelta();
    
    103
    +
    
    104
    +signals:
    
    105
    +  void wheelNavigate(int steps);
    
    106
    +  void wheelResize(int steps);
    
    107
    +  void wheelZoom(int steps);
    
    108
    +  void beginIndexChangeRequest(int newIndex);
    
    109
    +  void displayingCountUpdated(int newCount);
    
    110
    +
    
    111
    +protected:
    
    112
    +  void paintEvent(QPaintEvent* event) override;
    
    113
    +  void wheelEvent(QWheelEvent* event) override;
    
    114
    +  void resizeEvent(QResizeEvent* event) override;
    
    115
    +  void mousePressEvent(QMouseEvent* event) override;
    
    116
    +  void mouseMoveEvent(QMouseEvent* event) override;
    
    117
    +
    
    118
    +private:
    
    119
    +  Engine* engine_;
    
    120
    +  StringRenderer stringRenderer_;
    
    121
    +
    
    122
    +  QTimer* flashTimer_;
    
    123
    +  int flashRemainingCount_ = 0;
    
    124
    +  int flashGlyphIndex_ = -1;
    
    125
    +
    
    126
    +  Source source_ = SRC_AllGlyphs;
    
    127
    +  Mode mode_ = M_Normal;
    
    128
    +  int beginIndex_;
    
    129
    +  double boldX_, boldY_, slant_;
    
    130
    +  double strokeRadius_;
    
    131
    +  QString text_;
    
    132
    +  int sizeIndicatorOffset_ = 0; // For Waterfall Rendering...
    
    133
    +
    
    134
    +  bool mouseOperationEnabled_ = true;
    
    135
    +  int displayingCount_ = 0;
    
    136
    +  FT_Fixed strokeRadiusForSize_ = 0;
    
    137
    +  double scale_ = 1.0;
    
    138
    +  FT_Matrix shearMatrix_;
    
    139
    +
    
    140
    +  FT_Stroker stroker_;
    
    141
    +
    
    142
    +  std::vector<GlyphCacheLine> glyphCache_;
    
    143
    +  QColor backgroundColorCache_;
    
    144
    +  GlyphCacheLine* currentWritingLine_ = NULL;
    
    145
    +
    
    146
    +  // Mouse operation related fields
    
    147
    +  QPoint positionDelta_; // For dragging on the text to move
    
    148
    +  double prevHoriPosition_;
    
    149
    +  QPoint prevPositionDelta_ = { 0, 0 };
    
    150
    +  QPoint mouseDownPostition_ = { 0, 0 };
    
    151
    +  int prevIndex_ = -1;
    
    152
    +  int averageLineCount_ = 0;
    
    153
    +
    
    154
    +  void paintByRenderer();
    
    155
    +
    
    156
    +  // These two assume ownership of glyphs, but don't free them.
    
    157
    +  // However, remember to free the glyph returned from `transformGlyphStroked`
    
    158
    +  void transformGlyphFancy(FT_Glyph glyph);
    
    159
    +  FT_Glyph transformGlyphStroked(FT_Glyph glyph);
    
    160
    +
    
    161
    +  void paintCache(QPainter* painter);
    
    162
    +  void fillCache();
    
    163
    +  void prePaint();
    
    164
    +  void updateStroke();
    
    165
    +  void updateRendererText();
    
    166
    +  void preprocessGlyph(FT_Glyph* glyphPtr);
    
    167
    +  // Callbacks
    
    168
    +  void beginSaveLine(FT_Vector pos,
    
    169
    +                     double sizePoint);
    
    170
    +  void saveSingleGlyph(FT_Glyph glyph,
    
    171
    +                       FT_Vector penPos,
    
    172
    +                       GlyphContext gctx);
    
    173
    +  void saveSingleGlyphImage(QImage* image,
    
    174
    +                            QRect rect,
    
    175
    +                            FT_Vector penPos,
    
    176
    +                            FT_Vector advance,
    
    177
    +                            GlyphContext gctx);
    
    178
    +
    
    179
    +  // Funcs drawing from the cache
    
    180
    +  void beginDrawCacheLine(QPainter* painter,
    
    181
    +                          GlyphCacheLine& line);
    
    182
    +  void drawCacheGlyph(QPainter* painter,
    
    183
    +                      const GlyphCacheEntry& entry,
    
    184
    +                      bool colorInverted = false);
    
    185
    +
    
    186
    +  // Mouse operations
    
    187
    +  GlyphCacheEntry* findGlyphByMouse(QPoint position,
    
    188
    +                                    double* outSizePoint);
    
    189
    +  int calculateAverageLineCount();
    
    190
    +
    
    191
    +  void flashTimerFired();
    
    192
    +
    
    193
    +  // Mouse constants
    
    194
    +  constexpr static int ClickDragThreshold = 10;
    
    195
    +  constexpr static int HorizontalUnitLength = 100;
    
    196
    +  constexpr static int VerticalUnitLength = 150;
    
    197
    +
    
    198
    +  // Flash Timer constants
    
    199
    +  constexpr static int FlashIntervalMs = 250;
    
    200
    +  constexpr static int FlashDurationMs = 3000;
    
    201
    +};
    
    202
    +
    
    203
    +
    
    204
    +// end of glyphcontinuous.hpp

  • src/ftinspect/maingui.cpp
    ... ... @@ -164,8 +164,14 @@ MainGUI::onTripletChanged()
    164 164
     void
    
    165 165
     MainGUI::switchTab()
    
    166 166
     {
    
    167
    +  auto current = tabWidget_->currentWidget();
    
    167 168
       reloadCurrentTabFont();
    
    168
    -  lastTab_ = tabWidget_->currentWidget();
    
    169
    +
    
    170
    +  if (current == continuousTab_ && lastTab_ == singularTab_
    
    171
    +      && singularTab_->currentGlyph() >= 0)
    
    172
    +    continuousTab_->highlightGlyph(singularTab_->currentGlyph());
    
    173
    +
    
    174
    +  lastTab_ = current;
    
    169 175
     }
    
    170 176
     
    
    171 177
     
    
    ... ... @@ -215,6 +221,7 @@ MainGUI::createLayout()
    215 221
     
    
    216 222
       // right side
    
    217 223
       singularTab_ = new SingularTab(this, engine_);
    
    224
    +  continuousTab_ = new ContinuousTab(this, engine_);
    
    218 225
     
    
    219 226
       tabWidget_ = new QTabWidget(this);
    
    220 227
       tabWidget_->setObjectName("mainTab"); // for stylesheet
    
    ... ... @@ -222,10 +229,15 @@ MainGUI::createLayout()
    222 229
       // Note those two list must be in sync
    
    223 230
       tabs_.push_back(singularTab_);
    
    224 231
       tabWidget_->addTab(singularTab_, tr("Singular Grid View"));
    
    232
    +  tabs_.push_back(continuousTab_);
    
    233
    +  tabWidget_->addTab(continuousTab_, tr("Continuous View"));
    
    225 234
       lastTab_ = singularTab_;
    
    226 235
       
    
    227 236
       tabWidget_->setTabToolTip(0, tr("View single glyph in grid view.\n"
    
    228 237
                                       "For pixelwise inspection of the glyphs."));
    
    238
    +  tabWidget_->setTabToolTip(1, tr("View a string of glyphs continuously.\n"
    
    239
    +                                  "Show all glyphs in the font or render "
    
    240
    +                                  "strings."));
    
    229 241
       tripletSelector_ = new TripletSelector(this, engine_);
    
    230 242
     
    
    231 243
       rightLayout_ = new QVBoxLayout;
    

  • src/ftinspect/maingui.hpp
    ... ... @@ -10,6 +10,7 @@
    10 10
     #include "panels/settingpanel.hpp"
    
    11 11
     #include "panels/abstracttab.hpp"
    
    12 12
     #include "panels/singular.hpp"
    
    13
    +#include "panels/continuous.hpp"
    
    13 14
     
    
    14 15
     #include <vector>
    
    15 16
     #include <QAction>
    
    ... ... @@ -88,6 +89,7 @@ private:
    88 89
       QTabWidget* tabWidget_;
    
    89 90
       std::vector<AbstractTab*> tabs_;
    
    90 91
       SingularTab* singularTab_;
    
    92
    +  ContinuousTab* continuousTab_;
    
    91 93
       QWidget* lastTab_ = NULL;
    
    92 94
     
    
    93 95
       void openFonts(QStringList const& fileNames);
    

  • src/ftinspect/meson.build
    ... ... @@ -23,28 +23,40 @@ if qt5_dep.found()
    23 23
       sources = files([
    
    24 24
         'engine/engine.cpp',
    
    25 25
         'engine/fontfilemanager.cpp',
    
    26
    -    'engine/rendering.cpp',
    
    26
    +    'engine/charmap.cpp',
    
    27 27
         'engine/paletteinfo.cpp',
    
    28
    -    'engine/mmgx.cpp',
    
    28
    +    'engine/stringrenderer.cpp',
    
    29 29
         'engine/fontinfo.cpp',
    
    30
    +    'engine/fontinfonamesmapping.cpp',
    
    31
    +    'engine/mmgx.cpp',
    
    32
    +    'engine/rendering.cpp',
    
    33
    +    'engine/renderutils.cpp',
    
    34
    +    'engine/charmap.cpp',
    
    30 35
     
    
    31 36
         'glyphcomponents/glyphbitmap.cpp',
    
    32 37
         'glyphcomponents/glyphoutline.cpp',
    
    33 38
         'glyphcomponents/glyphpointnumbers.cpp',
    
    34 39
         'glyphcomponents/glyphpoints.cpp',
    
    40
    +    'glyphcomponents/glyphcontinuous.cpp',
    
    35 41
         'glyphcomponents/grid.cpp',
    
    36 42
         'glyphcomponents/graphicsdefault.cpp',
    
    37 43
     
    
    38 44
         'widgets/customwidgets.cpp',
    
    39
    -    'widgets/tripletselector.cpp',
    
    40 45
         'widgets/glyphindexselector.cpp',
    
    41 46
         'widgets/fontsizeselector.cpp',
    
    47
    +    'widgets/tripletselector.cpp',
    
    48
    +    'widgets/charmapcombobox.cpp',
    
    42 49
     
    
    43 50
         'models/customcomboboxmodels.cpp',
    
    51
    +    'models/fontinfomodels.cpp',
    
    44 52
     
    
    45 53
         'panels/settingpanel.cpp',
    
    46 54
         'panels/settingpanelmmgx.cpp',
    
    47 55
         'panels/singular.cpp',
    
    56
    +    'panels/continuous.cpp',
    
    57
    +    'panels/comparator.cpp',
    
    58
    +    'panels/glyphdetails.cpp',
    
    59
    +    'panels/info.cpp',
    
    48 60
     
    
    49 61
         'ftinspect.cpp',
    
    50 62
         'maingui.cpp',
    
    ... ... @@ -55,14 +67,22 @@ if qt5_dep.found()
    55 67
         moc_headers: [
    
    56 68
           'engine/fontfilemanager.hpp',
    
    57 69
           'widgets/customwidgets.hpp',
    
    58
    -      'widgets/tripletselector.hpp',
    
    59 70
           'widgets/glyphindexselector.hpp',
    
    60 71
           'widgets/fontsizeselector.hpp',
    
    61
    -      'maingui.hpp',
    
    72
    +      'widgets/tripletselector.hpp',
    
    73
    +      'widgets/charmapcombobox.hpp',
    
    74
    +      'glyphcomponents/glyphbitmap.hpp',
    
    75
    +      'glyphcomponents/glyphcontinuous.hpp',
    
    62 76
           'models/customcomboboxmodels.hpp',
    
    77
    +      'models/fontinfomodels.hpp',
    
    63 78
           'panels/settingpanel.hpp',
    
    64 79
           'panels/settingpanelmmgx.hpp',
    
    65 80
           'panels/singular.hpp',
    
    81
    +      'panels/continuous.hpp',
    
    82
    +      'panels/comparator.hpp',
    
    83
    +      'panels/glyphdetails.hpp',
    
    84
    +      'panels/info.hpp',
    
    85
    +      'maingui.hpp',
    
    66 86
         ],
    
    67 87
         dependencies: qt5_dep)
    
    68 88
     
    

  • src/ftinspect/panels/continuous.cpp
    1
    +// continuous.cpp
    
    2
    +
    
    3
    +// Copyright (C) 2022 by Charlie Jiang.
    
    4
    +
    
    5
    +#include "continuous.hpp"
    
    6
    +
    
    7
    +#include "../uihelper.hpp"
    
    8
    +
    
    9
    +#include <climits>
    
    10
    +#include <QToolTip>
    
    11
    +#include <QVariant>
    
    12
    +
    
    13
    +
    
    14
    +ContinuousTab::ContinuousTab(QWidget* parent,
    
    15
    +                             Engine* engine)
    
    16
    +: QWidget(parent),
    
    17
    +  engine_(engine)
    
    18
    +{
    
    19
    +  createLayout();
    
    20
    +
    
    21
    +  std::vector<CharMapInfo> tempCharMaps;
    
    22
    +  charMapSelector_->repopulate(tempCharMaps); // pass in an empty one
    
    23
    +
    
    24
    +  checkModeSource();
    
    25
    +  setDefaults();
    
    26
    +
    
    27
    +  createConnections();
    
    28
    +}
    
    29
    +
    
    30
    +
    
    31
    +void
    
    32
    +ContinuousTab::repaintGlyph()
    
    33
    +{
    
    34
    +  sizeSelector_->applyToEngine(engine_);
    
    35
    +  
    
    36
    +  applySettings();
    
    37
    +  canvas_->stopFlashing();
    
    38
    +  canvas_->purgeCache();
    
    39
    +  canvas_->repaint();
    
    40
    +}
    
    41
    +
    
    42
    +
    
    43
    +void
    
    44
    +ContinuousTab::reloadFont()
    
    45
    +{
    
    46
    +  currentGlyphCount_ = engine_->currentFontNumberOfGlyphs();
    
    47
    +  {
    
    48
    +    QSignalBlocker blocker(sizeSelector_);
    
    49
    +    sizeSelector_->reloadFromFont(engine_);
    
    50
    +  }
    
    51
    +  setGlyphCount(qBound(0, currentGlyphCount_, INT_MAX));
    
    52
    +  checkModeSource();
    
    53
    +
    
    54
    +  charMapSelector_->repopulate();
    
    55
    +  canvas_->stopFlashing();
    
    56
    +  canvas_->stringRenderer().reloadAll();
    
    57
    +  canvas_->purgeCache();
    
    58
    +  repaintGlyph();
    
    59
    +}
    
    60
    +
    
    61
    +
    
    62
    +void
    
    63
    +ContinuousTab::applySettings()
    
    64
    +{
    
    65
    +  auto mode = static_cast<GlyphContinuous::Mode>(modeSelector_->currentIndex());
    
    66
    +  auto src
    
    67
    +    = static_cast<GlyphContinuous::Source>(sourceSelector_->currentIndex());
    
    68
    +  canvas_->setMode(mode);
    
    69
    +  canvas_->setSource(src);
    
    70
    +  canvas_->setBeginIndex(indexSelector_->currentIndex());
    
    71
    +  canvas_->setScale(sizeSelector_->zoomFactor());
    
    72
    +  auto& sr = canvas_->stringRenderer();
    
    73
    +  sr.setWaterfall(waterfallCheckBox_->isChecked());
    
    74
    +  sr.setVertical(verticalCheckBox_->isChecked());
    
    75
    +  sr.setKerning(kerningCheckBox_->isChecked());
    
    76
    +  sr.setRotation(rotationSpinBox_->value());
    
    77
    +
    
    78
    +  // -1: Glyph order, otherwise the char map index in the original list
    
    79
    +  sr.setCharMapIndex(charMapSelector_->currentCharMapIndex(), glyphLimitIndex_);
    
    80
    +
    
    81
    +  if (sr.isWaterfall())
    
    82
    +    sr.setWaterfallParameters(wfConfigDialog_->startSize(),
    
    83
    +                              wfConfigDialog_->endSize());
    
    84
    +
    
    85
    +  canvas_->setFancyParams(xEmboldeningSpinBox_->value(),
    
    86
    +                          yEmboldeningSpinBox_->value(),
    
    87
    +                          slantSpinBox_->value());
    
    88
    +  canvas_->setStrokeRadius(strokeRadiusSpinBox_->value());
    
    89
    +}
    
    90
    +
    
    91
    +
    
    92
    +void
    
    93
    +ContinuousTab::highlightGlyph(int index)
    
    94
    +{
    
    95
    +  canvas_->flashOnGlyph(index);
    
    96
    +}
    
    97
    +
    
    98
    +
    
    99
    +void
    
    100
    +ContinuousTab::setGlyphCount(int count)
    
    101
    +{
    
    102
    +  currentGlyphCount_ = count;
    
    103
    +  updateLimitIndex();
    
    104
    +}
    
    105
    +
    
    106
    +
    
    107
    +void
    
    108
    +ContinuousTab::setGlyphBeginindex(int index)
    
    109
    +{
    
    110
    +  indexSelector_->setCurrentIndex(index);
    
    111
    +}
    
    112
    +
    
    113
    +
    
    114
    +void
    
    115
    +ContinuousTab::updateLimitIndex()
    
    116
    +{
    
    117
    +  auto cMap = charMapSelector_->currentCharMapIndex();
    
    118
    +  if (cMap < 0)
    
    119
    +    glyphLimitIndex_ = currentGlyphCount_;
    
    120
    +  else
    
    121
    +    glyphLimitIndex_ = charMapSelector_->charMaps()[cMap].maxIndex + 1;
    
    122
    +  indexSelector_->setMinMax(0, glyphLimitIndex_ - 1);
    
    123
    +}
    
    124
    +
    
    125
    +
    
    126
    +void
    
    127
    +ContinuousTab::checkModeSource()
    
    128
    +{
    
    129
    +  auto isFancy = modeSelector_->currentIndex() == GlyphContinuous::M_Fancy;
    
    130
    +  auto isStroked = modeSelector_->currentIndex() == GlyphContinuous::M_Stroked;
    
    131
    +  xEmboldeningSpinBox_->setEnabled(isFancy);
    
    132
    +  yEmboldeningSpinBox_->setEnabled(isFancy);
    
    133
    +  slantSpinBox_->setEnabled(isFancy);
    
    134
    +  strokeRadiusSpinBox_->setEnabled(isStroked);
    
    135
    +
    
    136
    +  auto src
    
    137
    +      = static_cast<GlyphContinuous::Source>(sourceSelector_->currentIndex());
    
    138
    +  auto isTextStrict = src == GlyphContinuous::SRC_TextString;
    
    139
    +  auto isText = src == GlyphContinuous::SRC_TextString
    
    140
    +                || src == GlyphContinuous::SRC_TextStringRepeated;
    
    141
    +  indexSelector_->setEnabled(src == GlyphContinuous::SRC_AllGlyphs);
    
    142
    +  sourceTextEdit_->setEnabled(isText);
    
    143
    +  sampleStringSelector_->setEnabled(isText);
    
    144
    +
    
    145
    +  {
    
    146
    +    QSignalBlocker blocker(kerningCheckBox_);
    
    147
    +    kerningCheckBox_->setEnabled(isText);
    
    148
    +    if (!isText)
    
    149
    +      kerningCheckBox_->setChecked(false);
    
    150
    +  }
    
    151
    +  
    
    152
    +  canvas_->setSource(src);
    
    153
    +
    
    154
    +  {
    
    155
    +    auto wf = waterfallCheckBox_->isChecked();
    
    156
    +    QSignalBlocker blocker(verticalCheckBox_);
    
    157
    +    if (wf || !isTextStrict)
    
    158
    +      verticalCheckBox_->setChecked(false);
    
    159
    +    verticalCheckBox_->setEnabled(!wf && isTextStrict);
    
    160
    +  }
    
    161
    +
    
    162
    +  {
    
    163
    +    auto vert = verticalCheckBox_->isChecked();
    
    164
    +    QSignalBlocker blocker(waterfallCheckBox_);
    
    165
    +    if (vert)
    
    166
    +      waterfallCheckBox_->setChecked(false);
    
    167
    +    waterfallCheckBox_->setEnabled(!vert);
    
    168
    +  }
    
    169
    +
    
    170
    +  waterfallConfigButton_->setEnabled(waterfallCheckBox_->isChecked()
    
    171
    +                                     && !engine_->currentFontBitmapOnly());
    
    172
    +}
    
    173
    +
    
    174
    +
    
    175
    +void
    
    176
    +ContinuousTab::checkModeSourceAndRepaint()
    
    177
    +{
    
    178
    +  checkModeSource();
    
    179
    +  repaintGlyph();
    
    180
    +}
    
    181
    +
    
    182
    +
    
    183
    +void
    
    184
    +ContinuousTab::charMapChanged()
    
    185
    +{
    
    186
    +  int newIndex = charMapSelector_->currentCharMapIndex();
    
    187
    +  if (newIndex != lastCharMapIndex_)
    
    188
    +    setGlyphBeginindex(charMapSelector_->defaultFirstGlyphIndex());
    
    189
    +  updateLimitIndex();
    
    190
    +
    
    191
    +  applySettings();
    
    192
    +  canvas_->stringRenderer().reloadAll();
    
    193
    +  repaintGlyph();
    
    194
    +  lastCharMapIndex_ = newIndex;
    
    195
    +}
    
    196
    +
    
    197
    +
    
    198
    +void
    
    199
    +ContinuousTab::sourceTextChanged()
    
    200
    +{
    
    201
    +  canvas_->setSourceText(sourceTextEdit_->toPlainText());
    
    202
    +  repaintGlyph();
    
    203
    +}
    
    204
    +
    
    205
    +
    
    206
    +void
    
    207
    +ContinuousTab::presetStringSelected()
    
    208
    +{
    
    209
    +  auto index = sampleStringSelector_->currentIndex();
    
    210
    +  if (index < 0)
    
    211
    +    return;
    
    212
    +
    
    213
    +  auto var = sampleStringSelector_->currentData();
    
    214
    +  if (var.isValid() && var.canConvert<QString>())
    
    215
    +  {
    
    216
    +    auto str = var.toString();
    
    217
    +    if (!str.isEmpty())
    
    218
    +      sourceTextEdit_->setPlainText(str);
    
    219
    +  }
    
    220
    +}
    
    221
    +
    
    222
    +
    
    223
    +void
    
    224
    +ContinuousTab::reloadGlyphsAndRepaint()
    
    225
    +{
    
    226
    +  canvas_->stringRenderer().reloadGlyphs();
    
    227
    +  repaintGlyph();
    
    228
    +}
    
    229
    +
    
    230
    +
    
    231
    +void
    
    232
    +ContinuousTab::openWaterfallConfig()
    
    233
    +{
    
    234
    +  wfConfigDialog_->setVisible(true); // no `exec`: modalless
    
    235
    +}
    
    236
    +
    
    237
    +
    
    238
    +void
    
    239
    +ContinuousTab::showToolTip()
    
    240
    +{
    
    241
    +  QToolTip::showText(mapToGlobal(helpButton_->pos()),
    
    242
    +                     tr(
    
    243
    +R"(Shift + Scroll: Adjust Font Size
    
    244
    +Ctrl + Scroll: Adjust Zoom Factor
    
    245
    +Shift + Plus/Minus: Adjust Font Size
    
    246
    +Shift + 0: Reset Font Size to Default
    
    247
    +Left Click: Show Glyph Details Info
    
    248
    +Right Click: Inspect Glyph in Singular Grid View
    
    249
    +
    
    250
    +<All Glyphs Source>
    
    251
    +  Drag: Adjust Begin Index
    
    252
    +<Text String Source>
    
    253
    +  Drag: Move String Position)"),
    
    254
    +                     helpButton_);
    
    255
    +}
    
    256
    +
    
    257
    +
    
    258
    +bool
    
    259
    +ContinuousTab::eventFilter(QObject* watched,
    
    260
    +                           QEvent* event)
    
    261
    +{
    
    262
    +  if (event->type() == QEvent::KeyPress)
    
    263
    +  {
    
    264
    +    auto keyEvent = dynamic_cast<QKeyEvent*>(event);
    
    265
    +    if (sizeSelector_->handleKeyEvent(keyEvent))
    
    266
    +      return true;
    
    267
    +  }
    
    268
    +  return false;
    
    269
    +}
    
    270
    +
    
    271
    +
    
    272
    +void
    
    273
    +ContinuousTab::wheelNavigate(int steps)
    
    274
    +{
    
    275
    +  if (sourceSelector_->currentIndex() == GlyphContinuous::SRC_AllGlyphs)
    
    276
    +    setGlyphBeginindex(indexSelector_->currentIndex() + steps);
    
    277
    +}
    
    278
    +
    
    279
    +
    
    280
    +void
    
    281
    +ContinuousTab::wheelZoom(int steps)
    
    282
    +{
    
    283
    +  sizeSelector_->handleWheelZoomBySteps(steps);
    
    284
    +}
    
    285
    +
    
    286
    +
    
    287
    +void
    
    288
    +ContinuousTab::wheelResize(int steps)
    
    289
    +{
    
    290
    +  sizeSelector_->handleWheelResizeBySteps(steps);
    
    291
    +}
    
    292
    +
    
    293
    +
    
    294
    +void
    
    295
    +ContinuousTab::createLayout()
    
    296
    +{
    
    297
    +  canvasFrame_ = new QFrame(this);
    
    298
    +  canvasFrame_->setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
    
    299
    +
    
    300
    +  canvas_ = new GlyphContinuous(canvasFrame_, engine_);
    
    301
    +  sizeSelector_ = new FontSizeSelector(this, false, true);
    
    302
    +
    
    303
    +  indexSelector_ = new GlyphIndexSelector(this);
    
    304
    +  indexSelector_->setSingleMode(false);
    
    305
    +  indexSelector_->setNumberRenderer([this](int index)
    
    306
    +                                    { return formatIndex(index); });
    
    307
    +  sourceTextEdit_ = new QPlainTextEdit(
    
    308
    +      tr("The quick brown fox jumps over the lazy dog."), this);
    
    309
    +
    
    310
    +  modeSelector_ = new QComboBox(this);
    
    311
    +  charMapSelector_ = new CharMapComboBox(this, engine_);
    
    312
    +  sourceSelector_ = new QComboBox(this);
    
    313
    +  sampleStringSelector_ = new QComboBox(this);
    
    314
    +
    
    315
    +  charMapSelector_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
    
    316
    +
    
    317
    +  // Note: in sync with the enum!!
    
    318
    +  modeSelector_->insertItem(GlyphContinuous::M_Normal, tr("Normal"));
    
    319
    +  modeSelector_->insertItem(GlyphContinuous::M_Fancy, tr("Fancy"));
    
    320
    +  modeSelector_->insertItem(GlyphContinuous::M_Stroked, tr("Stroked"));
    
    321
    +  modeSelector_->setCurrentIndex(GlyphContinuous::M_Normal);
    
    322
    +
    
    323
    +  // Note: in sync with the enum!!
    
    324
    +  sourceSelector_->insertItem(GlyphContinuous::SRC_AllGlyphs,
    
    325
    +                              tr("All Glyphs"));
    
    326
    +  sourceSelector_->insertItem(GlyphContinuous::SRC_TextString, 
    
    327
    +                              tr("Text String"));
    
    328
    +  sourceSelector_->insertItem(GlyphContinuous::SRC_TextStringRepeated,
    
    329
    +                              tr("Text String (Repeated)"));
    
    330
    +
    
    331
    +  verticalCheckBox_ = new QCheckBox(tr("Vertical"), this);
    
    332
    +  waterfallCheckBox_ = new QCheckBox(tr("Waterfall"), this);
    
    333
    +  kerningCheckBox_ = new QCheckBox(tr("Kerning"), this);
    
    334
    +
    
    335
    +  modeLabel_ = new QLabel(tr("Mode:"), this);
    
    336
    +  sourceLabel_ = new QLabel(tr("Text Source:"), this);
    
    337
    +  charMapLabel_ = new QLabel(tr("Char Map:"), this);
    
    338
    +  xEmboldeningLabel_ = new QLabel(tr("Horz. Emb.:"), this);
    
    339
    +  yEmboldeningLabel_ = new QLabel(tr("Vert. Emb.:"), this);
    
    340
    +  slantLabel_ = new QLabel(tr("Slanting:"), this);
    
    341
    +  strokeRadiusLabel_ = new QLabel(tr("Stroke Radius:"), this);
    
    342
    +  rotationLabel_ = new QLabel(tr("Rotation:"), this);
    
    343
    +
    
    344
    +  resetPositionButton_ = new QPushButton(tr("Reset Pos"), this);
    
    345
    +  waterfallConfigButton_ = new QPushButton(tr("WF Config"), this);
    
    346
    +  helpButton_ = new QPushButton(this);
    
    347
    +  helpButton_->setText(tr("?"));
    
    348
    +
    
    349
    +  xEmboldeningSpinBox_ = new QDoubleSpinBox(this);
    
    350
    +  yEmboldeningSpinBox_ = new QDoubleSpinBox(this);
    
    351
    +  slantSpinBox_ = new QDoubleSpinBox(this);
    
    352
    +  strokeRadiusSpinBox_ = new QDoubleSpinBox(this);
    
    353
    +  rotationSpinBox_ = new QDoubleSpinBox(this);
    
    354
    +
    
    355
    +  xEmboldeningSpinBox_->setSingleStep(0.005);
    
    356
    +  xEmboldeningSpinBox_->setMinimum(-0.1);
    
    357
    +  xEmboldeningSpinBox_->setMaximum(0.1);
    
    358
    +  yEmboldeningSpinBox_->setSingleStep(0.005);
    
    359
    +  yEmboldeningSpinBox_->setMinimum(-0.1);
    
    360
    +  yEmboldeningSpinBox_->setMaximum(0.1);
    
    361
    +  slantSpinBox_->setSingleStep(0.02);
    
    362
    +  slantSpinBox_->setMinimum(-1);
    
    363
    +  slantSpinBox_->setMaximum(1);
    
    364
    +  strokeRadiusSpinBox_->setSingleStep(0.005);
    
    365
    +  strokeRadiusSpinBox_->setMinimum(0);
    
    366
    +  strokeRadiusSpinBox_->setMaximum(0.05);
    
    367
    +  rotationSpinBox_->setSingleStep(5);
    
    368
    +  rotationSpinBox_->setMinimum(-180);
    
    369
    +  rotationSpinBox_->setMaximum(180);
    
    370
    +
    
    371
    +  wfConfigDialog_ = new WaterfallConfigDialog(this);
    
    372
    +
    
    373
    +  // Tooltips
    
    374
    +  sourceSelector_->setToolTip(tr("Choose what to display as the text source."));
    
    375
    +  modeSelector_->setToolTip(
    
    376
    +    tr("Choose the special effect in which the text is displayed."));
    
    377
    +  strokeRadiusSpinBox_->setToolTip(
    
    378
    +    tr("Stroke corner radius (only available when mode set to Stroked)"));
    
    379
    +  rotationSpinBox_->setToolTip(tr("Rotation, in degrees"));
    
    380
    +  xEmboldeningSpinBox_->setToolTip(
    
    381
    +    tr("Horizontal Emboldening (only available when mode set to Fancy)"));
    
    382
    +  yEmboldeningSpinBox_->setToolTip(
    
    383
    +    tr("Vertical Emboldening (only available when mode set to Fancy)"));
    
    384
    +  slantSpinBox_->setToolTip(
    
    385
    +    tr("Slanting (only available when mode set to Fancy)"));
    
    386
    +  sourceTextEdit_->setToolTip(
    
    387
    +    tr("Source string (only available when source set to Text String)"));
    
    388
    +  waterfallConfigButton_->setToolTip(tr(
    
    389
    +    "Set waterfall start and end size. Not available when the font is not\n"
    
    390
    +    "scalable because in such case all available sizes would be displayed."));
    
    391
    +  sampleStringSelector_->setToolTip(
    
    392
    +    tr("Select preset sample strings (only available when source set to\nText "
    
    393
    +       "String)"));
    
    394
    +  resetPositionButton_->setToolTip(tr("Reset the position to the center (only "
    
    395
    +                                      "available when source set to\nText "
    
    396
    +                                      "String)"));
    
    397
    +  waterfallCheckBox_->setToolTip(tr(
    
    398
    +    "Enable waterfall mode: show the font output in different sizes.\nWill "
    
    399
    +    "show all available sizes when the font is not scalable."));
    
    400
    +  verticalCheckBox_->setToolTip(tr("Enable vertical rendering (only available\n"
    
    401
    +                                "when source set to Text String)"));
    
    402
    +  kerningCheckBox_->setToolTip(tr("Enable kerning (GPOS table unsupported)"));
    
    403
    +  helpButton_->setToolTip(tr("Get mouse helps"));
    
    404
    +
    
    405
    +  // Layouting
    
    406
    +  canvasFrameLayout_ = new QHBoxLayout;
    
    407
    +  canvasFrameLayout_->addWidget(canvas_);
    
    408
    +  canvasFrame_->setLayout(canvasFrameLayout_);
    
    409
    +  canvasFrameLayout_->setContentsMargins(2, 2, 2, 2);
    
    410
    +  canvasFrame_->setContentsMargins(2, 2, 2, 2);
    
    411
    +
    
    412
    +  sizeHelpLayout_ = new QHBoxLayout;
    
    413
    +  sizeHelpLayout_->addWidget(sizeSelector_, 1, Qt::AlignVCenter);
    
    414
    +  sizeHelpLayout_->addWidget(helpButton_, 0);
    
    415
    +
    
    416
    +  bottomLayout_ = new QGridLayout;
    
    417
    +  bottomLayout_->addWidget(sourceLabel_, 0, 0);
    
    418
    +  bottomLayout_->addWidget(modeLabel_, 1, 0);
    
    419
    +  bottomLayout_->addWidget(charMapLabel_, 2, 0);
    
    420
    +  bottomLayout_->addWidget(sourceSelector_, 0, 1);
    
    421
    +  bottomLayout_->addWidget(modeSelector_, 1, 1);
    
    422
    +  bottomLayout_->addWidget(charMapSelector_, 2, 1);
    
    423
    +
    
    424
    +  bottomLayout_->addWidget(xEmboldeningLabel_, 1, 2);
    
    425
    +  bottomLayout_->addWidget(yEmboldeningLabel_, 2, 2);
    
    426
    +  bottomLayout_->addWidget(slantLabel_, 3, 2);
    
    427
    +  bottomLayout_->addWidget(strokeRadiusLabel_, 3, 0);
    
    428
    +  bottomLayout_->addWidget(rotationLabel_, 0, 2);
    
    429
    +
    
    430
    +  bottomLayout_->addWidget(xEmboldeningSpinBox_, 1, 3);
    
    431
    +  bottomLayout_->addWidget(yEmboldeningSpinBox_, 2, 3);
    
    432
    +  bottomLayout_->addWidget(slantSpinBox_, 3, 3);
    
    433
    +  bottomLayout_->addWidget(strokeRadiusSpinBox_, 3, 1);
    
    434
    +  bottomLayout_->addWidget(rotationSpinBox_, 0, 3);
    
    435
    +
    
    436
    +  bottomLayout_->addWidget(indexSelector_, 0, 4, 1, 2);
    
    437
    +  bottomLayout_->addWidget(sourceTextEdit_, 1, 4, 3, 1);
    
    438
    +  bottomLayout_->addWidget(resetPositionButton_, 0, 6);
    
    439
    +  bottomLayout_->addWidget(waterfallCheckBox_, 1, 6);
    
    440
    +  bottomLayout_->addWidget(verticalCheckBox_, 2, 6);
    
    441
    +  bottomLayout_->addWidget(kerningCheckBox_, 3, 6);
    
    442
    +  bottomLayout_->addWidget(waterfallConfigButton_, 1, 5);
    
    443
    +  bottomLayout_->addWidget(sampleStringSelector_, 2, 5);
    
    444
    +
    
    445
    +  bottomLayout_->setColumnStretch(4, 1);
    
    446
    +
    
    447
    +  mainLayout_ = new QVBoxLayout;
    
    448
    +  mainLayout_->addWidget(canvasFrame_);
    
    449
    +  mainLayout_->addLayout(sizeHelpLayout_);
    
    450
    +  mainLayout_->addLayout(bottomLayout_);
    
    451
    +
    
    452
    +  setLayout(mainLayout_);
    
    453
    +}
    
    454
    +
    
    455
    +
    
    456
    +void
    
    457
    +ContinuousTab::createConnections()
    
    458
    +{
    
    459
    +  connect(sizeSelector_, &FontSizeSelector::valueChanged,
    
    460
    +          this, &ContinuousTab::reloadGlyphsAndRepaint);
    
    461
    +
    
    462
    +  connect(canvas_, &GlyphContinuous::wheelResize, 
    
    463
    +          this, &ContinuousTab::wheelResize);
    
    464
    +  connect(canvas_, &GlyphContinuous::wheelNavigate, 
    
    465
    +          this, &ContinuousTab::wheelNavigate);
    
    466
    +  connect(canvas_, &GlyphContinuous::wheelZoom, 
    
    467
    +          this, &ContinuousTab::wheelZoom);
    
    468
    +  connect(canvas_, &GlyphContinuous::displayingCountUpdated, 
    
    469
    +          indexSelector_, &GlyphIndexSelector::setShowingCount);
    
    470
    +  connect(canvas_, &GlyphContinuous::beginIndexChangeRequest, 
    
    471
    +          this, &ContinuousTab::setGlyphBeginindex);
    
    472
    +
    
    473
    +  connect(indexSelector_, &GlyphIndexSelector::currentIndexChanged,
    
    474
    +          this, &ContinuousTab::repaintGlyph);
    
    475
    +  connect(modeSelector_, QOverload<int>::of(&QComboBox::currentIndexChanged),
    
    476
    +          this, &ContinuousTab::checkModeSourceAndRepaint);
    
    477
    +  connect(charMapSelector_,
    
    478
    +          QOverload<int>::of(&CharMapComboBox::currentIndexChanged),
    
    479
    +          this, &ContinuousTab::charMapChanged);
    
    480
    +  connect(charMapSelector_, &CharMapComboBox::forceUpdateLimitIndex,
    
    481
    +          this, &ContinuousTab::updateLimitIndex);
    
    482
    +  connect(sourceSelector_, QOverload<int>::of(&QComboBox::currentIndexChanged),
    
    483
    +          this, &ContinuousTab::checkModeSourceAndRepaint);
    
    484
    +
    
    485
    +  connect(resetPositionButton_, &QPushButton::clicked,
    
    486
    +          canvas_, &GlyphContinuous::resetPositionDelta);
    
    487
    +  connect(waterfallConfigButton_, &QPushButton::clicked,
    
    488
    +          this, &ContinuousTab::openWaterfallConfig);
    
    489
    +  connect(helpButton_, &QPushButton::clicked,
    
    490
    +          this, &ContinuousTab::showToolTip);
    
    491
    +  connect(wfConfigDialog_, &WaterfallConfigDialog::sizeUpdated,
    
    492
    +          this, &ContinuousTab::repaintGlyph);
    
    493
    +
    
    494
    +  connect(xEmboldeningSpinBox_, 
    
    495
    +          QOverload<double>::of(&QDoubleSpinBox::valueChanged),
    
    496
    +          this, &ContinuousTab::repaintGlyph);
    
    497
    +  connect(yEmboldeningSpinBox_, 
    
    498
    +          QOverload<double>::of(&QDoubleSpinBox::valueChanged),
    
    499
    +          this, &ContinuousTab::repaintGlyph);
    
    500
    +  connect(slantSpinBox_, 
    
    501
    +          QOverload<double>::of(&QDoubleSpinBox::valueChanged),
    
    502
    +          this, &ContinuousTab::repaintGlyph);
    
    503
    +  connect(strokeRadiusSpinBox_, 
    
    504
    +          QOverload<double>::of(&QDoubleSpinBox::valueChanged),
    
    505
    +          this, &ContinuousTab::repaintGlyph);
    
    506
    +  connect(rotationSpinBox_, 
    
    507
    +          QOverload<double>::of(&QDoubleSpinBox::valueChanged),
    
    508
    +          this, &ContinuousTab::repaintGlyph);
    
    509
    +
    
    510
    +  connect(waterfallCheckBox_, &QCheckBox::clicked,
    
    511
    +          this, &ContinuousTab::checkModeSourceAndRepaint);
    
    512
    +  connect(verticalCheckBox_, &QCheckBox::clicked,
    
    513
    +          this, &ContinuousTab::checkModeSourceAndRepaint);
    
    514
    +  connect(kerningCheckBox_, &QCheckBox::clicked,
    
    515
    +          this, &ContinuousTab::reloadGlyphsAndRepaint);
    
    516
    +  connect(sourceTextEdit_, &QPlainTextEdit::textChanged,
    
    517
    +          this, &ContinuousTab::sourceTextChanged);
    
    518
    +  connect(sampleStringSelector_, 
    
    519
    +          QOverload<int>::of(&QComboBox::currentIndexChanged),
    
    520
    +          this, &ContinuousTab::presetStringSelected);
    
    521
    +
    
    522
    +  sizeSelector_->installEventFilterForWidget(canvas_);
    
    523
    +  sizeSelector_->installEventFilterForWidget(this);
    
    524
    +}
    
    525
    +
    
    526
    +
    
    527
    +extern const char* StringSamples[];
    
    528
    +
    
    529
    +void
    
    530
    +ContinuousTab::setDefaults()
    
    531
    +{
    
    532
    +  xEmboldeningSpinBox_->setValue(0.04);
    
    533
    +  yEmboldeningSpinBox_->setValue(0.04);
    
    534
    +  slantSpinBox_->setValue(0.22);
    
    535
    +  strokeRadiusSpinBox_->setValue(0.02);
    
    536
    +  rotationSpinBox_->setValue(0);
    
    537
    +
    
    538
    +  canvas_->setSourceText(sourceTextEdit_->toPlainText());
    
    539
    +  canvas_->setSource(GlyphContinuous::SRC_AllGlyphs);
    
    540
    +
    
    541
    +  sampleStringSelector_->addItem(tr("<Sample>"));
    
    542
    +  sampleStringSelector_->addItem(tr("English"),  QString(StringSamples[0]));
    
    543
    +  sampleStringSelector_->addItem(tr("Latin"),    QString(StringSamples[1]));
    
    544
    +  sampleStringSelector_->addItem(tr("Greek"),    QString(StringSamples[2]));
    
    545
    +  sampleStringSelector_->addItem(tr("Cyrillic"), QString(StringSamples[3]));
    
    546
    +  sampleStringSelector_->addItem(tr("Chinese"),  QString(StringSamples[4]));
    
    547
    +  sampleStringSelector_->addItem(tr("Japanese"), QString(StringSamples[5]));
    
    548
    +  sampleStringSelector_->addItem(tr("Korean"),   QString(StringSamples[6]));
    
    549
    +}
    
    550
    +
    
    551
    +
    
    552
    +QString
    
    553
    +ContinuousTab::formatIndex(int index)
    
    554
    +{
    
    555
    +  auto idx = charMapSelector_->currentCharMapIndex();
    
    556
    +  if (idx < 0) // glyph order
    
    557
    +    return QString::number(index);
    
    558
    +  return charMapSelector_->charMaps()[idx].stringifyIndexShort(index);
    
    559
    +}
    
    560
    +
    
    561
    +
    
    562
    +WaterfallConfigDialog::WaterfallConfigDialog(QWidget* parent)
    
    563
    +: QDialog(parent)
    
    564
    +{
    
    565
    +  setModal(false);
    
    566
    +  setWindowTitle(tr("Waterfall Config"));
    
    567
    +  setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
    
    568
    +
    
    569
    +  createLayout();
    
    570
    +  checkAutoStatus();
    
    571
    +  createConnections();
    
    572
    +}
    
    573
    +
    
    574
    +
    
    575
    +double
    
    576
    +WaterfallConfigDialog::startSize()
    
    577
    +{
    
    578
    +  if (autoBox_->isChecked())
    
    579
    +    return -1.0;
    
    580
    +  return startSpinBox_->value();
    
    581
    +}
    
    582
    +
    
    583
    +
    
    584
    +double
    
    585
    +WaterfallConfigDialog::endSize()
    
    586
    +{
    
    587
    +  if (autoBox_->isChecked())
    
    588
    +    return -1.0;
    
    589
    +  return endSpinBox_->value();
    
    590
    +}
    
    591
    +
    
    592
    +
    
    593
    +void
    
    594
    +WaterfallConfigDialog::createLayout()
    
    595
    +{
    
    596
    +  startLabel_ = new QLabel(tr("Start Size (pt):"), this);
    
    597
    +  endLabel_ = new QLabel(tr("End Size (pt):"), this);
    
    598
    +
    
    599
    +  startSpinBox_ = new QDoubleSpinBox(this);
    
    600
    +  endSpinBox_ = new QDoubleSpinBox(this);
    
    601
    +
    
    602
    +  startSpinBox_->setSingleStep(0.5);
    
    603
    +  startSpinBox_->setMinimum(0.5);
    
    604
    +  startSpinBox_->setValue(1);
    
    605
    +
    
    606
    +  endSpinBox_->setSingleStep(0.5);
    
    607
    +  endSpinBox_->setMinimum(0.5);
    
    608
    +  endSpinBox_->setValue(1);
    
    609
    +
    
    610
    +  autoBox_ = new QCheckBox(tr("Auto"), this);
    
    611
    +  autoBox_->setChecked(true);
    
    612
    +
    
    613
    +  // Tooltips
    
    614
    +  autoBox_->setToolTip(tr(
    
    615
    +    "Use the default value which will try to start from near zero and place "
    
    616
    +    "in the middle of the screen the size selected in the selector."));
    
    617
    +  startSpinBox_->setToolTip(tr("Start size, will be always guaranteed."));
    
    618
    +  endSpinBox_->setToolTip(tr(
    
    619
    +    "End size, may not be guaranteed due to rounding and precision issues."));
    
    620
    +
    
    621
    +  // Layouting
    
    622
    +  layout_ = new QGridLayout;
    
    623
    +  gridLayout2ColAddWidget(layout_, autoBox_);
    
    624
    +  gridLayout2ColAddWidget(layout_, startLabel_, startSpinBox_);
    
    625
    +  gridLayout2ColAddWidget(layout_, endLabel_, endSpinBox_);
    
    626
    +
    
    627
    +  setLayout(layout_);
    
    628
    +}
    
    629
    +
    
    630
    +
    
    631
    +void
    
    632
    +WaterfallConfigDialog::createConnections()
    
    633
    +{
    
    634
    +  connect(autoBox_, &QCheckBox::clicked,
    
    635
    +          this, &WaterfallConfigDialog::checkAutoStatus);
    
    636
    +  connect(startSpinBox_, 
    
    637
    +          QOverload<double>::of(&QDoubleSpinBox::valueChanged),
    
    638
    +          this, &WaterfallConfigDialog::sizeUpdated);
    
    639
    +  connect(endSpinBox_, 
    
    640
    +          QOverload<double>::of(&QDoubleSpinBox::valueChanged),
    
    641
    +          this, &WaterfallConfigDialog::sizeUpdated);
    
    642
    +}
    
    643
    +
    
    644
    +
    
    645
    +void
    
    646
    +WaterfallConfigDialog::checkAutoStatus()
    
    647
    +{
    
    648
    +  startSpinBox_->setEnabled(!autoBox_->isChecked());
    
    649
    +  endSpinBox_->setEnabled(!autoBox_->isChecked());
    
    650
    +
    
    651
    +  emit sizeUpdated();
    
    652
    +}
    
    653
    +
    
    654
    +
    
    655
    +const char* StringSamples[] = {
    
    656
    +  "The quick brown fox jumps over the lazy dog",
    
    657
    +
    
    658
    +  /* Luís argüia à Júlia que «brações, fé, chá, óxido, pôr, zângão» */
    
    659
    +  /* eram palavras do português */
    
    660
    +  "Lu\u00EDs arg\u00FCia \u00E0 J\u00FAlia que \u00ABbra\u00E7\u00F5es, "
    
    661
    +  "f\u00E9, ch\u00E1, \u00F3xido, p\u00F4r, z\u00E2ng\u00E3o\u00BB eram "
    
    662
    +  "palavras do portugu\u00EAs",
    
    663
    +
    
    664
    +  /* Ο καλύμνιος σφουγγαράς ψιθύρισε πως θα βουτήξει χωρίς να διστάζει */
    
    665
    +  "\u039F \u03BA\u03B1\u03BB\u03CD\u03BC\u03BD\u03B9\u03BF\u03C2 \u03C3"
    
    666
    +  "\u03C6\u03BF\u03C5\u03B3\u03B3\u03B1\u03C1\u03AC\u03C2 \u03C8\u03B9"
    
    667
    +  "\u03B8\u03CD\u03C1\u03B9\u03C3\u03B5 \u03C0\u03C9\u03C2 \u03B8\u03B1 "
    
    668
    +  "\u03B2\u03BF\u03C5\u03C4\u03AE\u03BE\u03B5\u03B9 \u03C7\u03C9\u03C1"
    
    669
    +  "\u03AF\u03C2 \u03BD\u03B1 \u03B4\u03B9\u03C3\u03C4\u03AC\u03B6\u03B5"
    
    670
    +  "\u03B9",
    
    671
    +
    
    672
    +  /* Съешь ещё этих мягких французских булок да выпей же чаю */
    
    673
    +  "\u0421\u044A\u0435\u0448\u044C \u0435\u0449\u0451 \u044D\u0442\u0438"
    
    674
    +  "\u0445 \u043C\u044F\u0433\u043A\u0438\u0445 \u0444\u0440\u0430\u043D"
    
    675
    +  "\u0446\u0443\u0437\u0441\u043A\u0438\u0445 \u0431\u0443\u043B\u043E"
    
    676
    +  "\u043A \u0434\u0430 \u0432\u044B\u043F\u0435\u0439 \u0436\u0435 "
    
    677
    +  "\u0447\u0430\u044E",
    
    678
    +
    
    679
    +  /* 天地玄黃,宇宙洪荒。日月盈昃,辰宿列張。寒來暑往,秋收冬藏。*/
    
    680
    +  "\u5929\u5730\u7384\u9EC3\uFF0C\u5B87\u5B99\u6D2A\u8352\u3002\u65E5"
    
    681
    +  "\u6708\u76C8\u6603\uFF0C\u8FB0\u5BBF\u5217\u5F35\u3002\u5BD2\u4F86"
    
    682
    +  "\u6691\u5F80\uFF0C\u79CB\u6536\u51AC\u85CF\u3002",
    
    683
    +
    
    684
    +  /* いろはにほへと ちりぬるを わかよたれそ つねならむ */
    
    685
    +  /* うゐのおくやま けふこえて あさきゆめみし ゑひもせす */
    
    686
    +  "\u3044\u308D\u306F\u306B\u307B\u3078\u3068 \u3061\u308A\u306C\u308B"
    
    687
    +  "\u3092 \u308F\u304B\u3088\u305F\u308C\u305D \u3064\u306D\u306A\u3089"
    
    688
    +  "\u3080 \u3046\u3090\u306E\u304A\u304F\u3084\u307E \u3051\u3075\u3053"
    
    689
    +  "\u3048\u3066 \u3042\u3055\u304D\u3086\u3081\u307F\u3057 \u3091\u3072"
    
    690
    +  "\u3082\u305B\u3059",
    
    691
    +
    
    692
    +  /* 키스의 고유조건은 입술끼리 만나야 하고 특별한 기술은 필요치 않다 */
    
    693
    +  "\uD0A4\uC2A4\uC758 \uACE0\uC720\uC870\uAC74\uC740 \uC785\uC220\uB07C"
    
    694
    +  "\uB9AC \uB9CC\uB098\uC57C \uD558\uACE0 \uD2B9\uBCC4\uD55C \uAE30"
    
    695
    +  "\uC220\uC740 \uD544\uC694\uCE58 \uC54A\uB2E4"
    
    696
    +};
    
    697
    +
    
    698
    +
    
    699
    +// end of continuous.cpp

  • src/ftinspect/panels/continuous.hpp
    1
    +// continuous.hpp
    
    2
    +
    
    3
    +// Copyright (C) 2022 by Charlie Jiang.
    
    4
    +
    
    5
    +#pragma once
    
    6
    +
    
    7
    +#include "abstracttab.hpp"
    
    8
    +#include "../widgets/customwidgets.hpp"
    
    9
    +#include "../widgets/glyphindexselector.hpp"
    
    10
    +#include "../widgets/fontsizeselector.hpp"
    
    11
    +#include "../widgets/charmapcombobox.hpp"
    
    12
    +#include "../glyphcomponents/graphicsdefault.hpp"
    
    13
    +#include "../glyphcomponents/glyphcontinuous.hpp"
    
    14
    +#include "../engine/engine.hpp"
    
    15
    +
    
    16
    +#include <vector>
    
    17
    +#include <QWidget>
    
    18
    +#include <QDialog>
    
    19
    +#include <QFrame>
    
    20
    +#include <QLabel>
    
    21
    +#include <QComboBox>
    
    22
    +#include <QGridLayout>
    
    23
    +#include <QBoxLayout>
    
    24
    +#include <QPlainTextEdit>
    
    25
    +#include <QCheckBox>
    
    26
    +
    
    27
    +class WaterfallConfigDialog;
    
    28
    +class ContinuousTab
    
    29
    +: public QWidget, public AbstractTab
    
    30
    +{
    
    31
    +  Q_OBJECT
    
    32
    +public:
    
    33
    +  ContinuousTab(QWidget* parent, Engine* engine);
    
    34
    +  ~ContinuousTab() override = default;
    
    35
    +
    
    36
    +  void repaintGlyph() override;
    
    37
    +  void reloadFont() override;
    
    38
    +  void highlightGlyph(int index);
    
    39
    +  void applySettings();
    
    40
    +
    
    41
    +protected:
    
    42
    +  bool eventFilter(QObject* watched, QEvent* event) override;
    
    43
    +  
    
    44
    +private:
    
    45
    +  Engine* engine_;
    
    46
    +
    
    47
    +  int currentGlyphCount_;
    
    48
    +  int lastCharMapIndex_ = 0;
    
    49
    +  int glyphLimitIndex_ = 0;
    
    50
    +
    
    51
    +  GlyphContinuous* canvas_;
    
    52
    +  QFrame* canvasFrame_;
    
    53
    +  FontSizeSelector* sizeSelector_;
    
    54
    +
    
    55
    +  QComboBox* modeSelector_;
    
    56
    +  QComboBox* sourceSelector_;
    
    57
    +  CharMapComboBox* charMapSelector_ = NULL;
    
    58
    +  QComboBox* sampleStringSelector_;
    
    59
    +
    
    60
    +  QPushButton* resetPositionButton_;
    
    61
    +  QPushButton* waterfallConfigButton_;
    
    62
    +  QPushButton* helpButton_;
    
    63
    +
    
    64
    +  QLabel* modeLabel_;
    
    65
    +  QLabel* sourceLabel_;
    
    66
    +  QLabel* charMapLabel_;
    
    67
    +  QLabel* xEmboldeningLabel_;
    
    68
    +  QLabel* yEmboldeningLabel_;
    
    69
    +  QLabel* slantLabel_;
    
    70
    +  QLabel* strokeRadiusLabel_;
    
    71
    +  QLabel* rotationLabel_;
    
    72
    +
    
    73
    +  QDoubleSpinBox* xEmboldeningSpinBox_;
    
    74
    +  QDoubleSpinBox* yEmboldeningSpinBox_;
    
    75
    +  QDoubleSpinBox* slantSpinBox_;
    
    76
    +  QDoubleSpinBox* strokeRadiusSpinBox_;
    
    77
    +  QDoubleSpinBox* rotationSpinBox_;
    
    78
    +
    
    79
    +  QCheckBox* verticalCheckBox_;
    
    80
    +  QCheckBox* waterfallCheckBox_;
    
    81
    +  QCheckBox* kerningCheckBox_;
    
    82
    +
    
    83
    +  GlyphIndexSelector* indexSelector_;
    
    84
    +  QPlainTextEdit* sourceTextEdit_;
    
    85
    +  
    
    86
    +  QHBoxLayout* canvasFrameLayout_;
    
    87
    +  QHBoxLayout* sizeHelpLayout_;
    
    88
    +  QGridLayout* bottomLayout_;
    
    89
    +  QVBoxLayout* mainLayout_;
    
    90
    +
    
    91
    +  WaterfallConfigDialog* wfConfigDialog_;
    
    92
    +
    
    93
    +  void createLayout();
    
    94
    +  void createConnections();
    
    95
    +
    
    96
    +  void updateLimitIndex();
    
    97
    +  void checkModeSource();
    
    98
    +
    
    99
    +  // This doesn't trigger immediate repaint
    
    100
    +  void setGlyphCount(int count);
    
    101
    +
    
    102
    +  // But they do
    
    103
    +  void setGlyphBeginindex(int index);
    
    104
    +  void checkModeSourceAndRepaint();
    
    105
    +  void charMapChanged();
    
    106
    +  void sourceTextChanged();
    
    107
    +  void presetStringSelected();
    
    108
    +  void reloadGlyphsAndRepaint();
    
    109
    +  void openWaterfallConfig();
    
    110
    +  void showToolTip();
    
    111
    +
    
    112
    +  void wheelNavigate(int steps);
    
    113
    +  void wheelZoom(int steps);
    
    114
    +  void wheelResize(int steps);
    
    115
    +
    
    116
    +  void setDefaults();
    
    117
    +  QString formatIndex(int index);
    
    118
    +};
    
    119
    +
    
    120
    +
    
    121
    +class WaterfallConfigDialog
    
    122
    +: public QDialog
    
    123
    +{
    
    124
    +  Q_OBJECT
    
    125
    +
    
    126
    +public:
    
    127
    +  WaterfallConfigDialog(QWidget* parent);
    
    128
    +
    
    129
    +  double startSize();
    
    130
    +  double endSize();
    
    131
    +
    
    132
    +signals:
    
    133
    +  void sizeUpdated();
    
    134
    +
    
    135
    +private:
    
    136
    +  QLabel* startLabel_;
    
    137
    +  QLabel* endLabel_;
    
    138
    +
    
    139
    +  QDoubleSpinBox* startSpinBox_;
    
    140
    +  QDoubleSpinBox* endSpinBox_;
    
    141
    +
    
    142
    +  QCheckBox* autoBox_;
    
    143
    +
    
    144
    +  QGridLayout* layout_;
    
    145
    +
    
    146
    +  void createLayout();
    
    147
    +  void createConnections();
    
    148
    +
    
    149
    +  void checkAutoStatus();
    
    150
    +};
    
    151
    +
    
    152
    +
    
    153
    +// end of continuous.hpp

  • src/ftinspect/widgets/charmapcombobox.cpp
    1
    +// charmapcombobox.cpp
    
    2
    +
    
    3
    +// Copyright (C) 2022 by Charlie Jiang.
    
    4
    +
    
    5
    +#include "charmapcombobox.hpp"
    
    6
    +
    
    7
    +#include "../engine/engine.hpp"
    
    8
    +
    
    9
    +
    
    10
    +CharMapComboBox::CharMapComboBox(QWidget* parent,
    
    11
    +                                 Engine* engine,
    
    12
    +                                 bool haveGlyphOrder)
    
    13
    +: QComboBox(parent),
    
    14
    +  haveGlyphOrder_(haveGlyphOrder),
    
    15
    +  engine_(engine)
    
    16
    +{
    
    17
    +  setToolTip("Set current charmap.");
    
    18
    +  connect(this, QOverload<int>::of(&CharMapComboBox::currentIndexChanged),
    
    19
    +          this, &CharMapComboBox::updateToolTip);
    
    20
    +}
    
    21
    +
    
    22
    +
    
    23
    +int
    
    24
    +CharMapComboBox::currentCharMapIndex()
    
    25
    +{
    
    26
    +  auto index = haveGlyphOrder_ ? currentIndex() - 1 : currentIndex();
    
    27
    +  if (index < 0 || charMaps_.size() <= static_cast<unsigned>(index))
    
    28
    +    return -1;
    
    29
    +  return index;
    
    30
    +}
    
    31
    +
    
    32
    +
    
    33
    +int
    
    34
    +CharMapComboBox::defaultFirstGlyphIndex()
    
    35
    +{
    
    36
    +  auto newIndex = currentCharMapIndex();
    
    37
    +  if (newIndex < 0)
    
    38
    +    return 0;
    
    39
    +  if (charMaps_[newIndex].maxIndex <= 20)
    
    40
    +    return charMaps_[newIndex].maxIndex - 1;
    
    41
    +  return 0x20;
    
    42
    +}
    
    43
    +
    
    44
    +
    
    45
    +void
    
    46
    +CharMapComboBox::repopulate()
    
    47
    +{
    
    48
    +  repopulate(engine_->currentFontCharMaps());
    
    49
    +}
    
    50
    +
    
    51
    +
    
    52
    +#define EncodingRole (Qt::UserRole + 10)
    
    53
    +void
    
    54
    +CharMapComboBox::repopulate(std::vector<CharMapInfo>& charMaps)
    
    55
    +{
    
    56
    +  if (charMaps_ == charMaps)
    
    57
    +  {
    
    58
    +    charMaps_ = charMaps; // Still need to substitute because ptr may differ
    
    59
    +    return;
    
    60
    +  }
    
    61
    +  charMaps_ = charMaps;
    
    62
    +  int oldIndex = currentIndex();
    
    63
    +  unsigned oldEncoding = 0u;
    
    64
    +
    
    65
    +  // Using additional UserRole to store encoding id
    
    66
    +  auto oldEncodingV = itemData(oldIndex, EncodingRole);
    
    67
    +  if (oldEncodingV.isValid() && oldEncodingV.canConvert<unsigned>())
    
    68
    +    oldEncoding = oldEncodingV.value<unsigned>();
    
    69
    +
    
    70
    +  { // This brace isn't for the `if` statement!
    
    71
    +    // suppress events during updating
    
    72
    +    QSignalBlocker selectorBlocker(this);
    
    73
    +
    
    74
    +    clear();
    
    75
    +    if (haveGlyphOrder_)
    
    76
    +    {
    
    77
    +      addItem(tr("Glyph Order"));
    
    78
    +      setItemData(0, 0u, EncodingRole);
    
    79
    +    }
    
    80
    +
    
    81
    +    int i = 0;
    
    82
    +    int newIndex = 0;
    
    83
    +    for (auto& map : charMaps)
    
    84
    +    {
    
    85
    +      addItem(tr("%1: %2 (platform %3, encoding %4)")
    
    86
    +                .arg(i)
    
    87
    +                .arg(*map.encodingName)
    
    88
    +                .arg(map.platformID)
    
    89
    +                .arg(map.encodingID));
    
    90
    +      auto encoding = static_cast<unsigned>(map.encoding);
    
    91
    +      setItemData(haveGlyphOrder_ ? i + 1 : i, encoding, EncodingRole);
    
    92
    +
    
    93
    +      if (encoding == oldEncoding && i == oldIndex)
    
    94
    +        newIndex = i;
    
    95
    +    
    
    96
    +      i++;
    
    97
    +    }
    
    98
    +
    
    99
    +    // this shouldn't emit any event either, because force repainting
    
    100
    +    // will happen later, so embrace it into blocker block
    
    101
    +    setCurrentIndex(newIndex);
    
    102
    +    updateToolTip();
    
    103
    +  }
    
    104
    +
    
    105
    +  emit forceUpdateLimitIndex();
    
    106
    +}
    
    107
    +
    
    108
    +
    
    109
    +void
    
    110
    +CharMapComboBox::updateToolTip()
    
    111
    +{
    
    112
    +  auto index = currentIndex();
    
    113
    +  if (index >= 0 && index < count())
    
    114
    +    setToolTip(this->currentText());
    
    115
    +}
    
    116
    +
    
    117
    +
    
    118
    +// end of charmapcombobox.cpp

  • src/ftinspect/widgets/charmapcombobox.hpp
    1
    +// charmapcombobox.hpp
    
    2
    +
    
    3
    +// Copyright (C) 2022 by Charlie Jiang.
    
    4
    +
    
    5
    +#pragma once
    
    6
    +
    
    7
    +#include "../engine/charmap.hpp"
    
    8
    +
    
    9
    +#include <vector>
    
    10
    +#include <QComboBox>
    
    11
    +
    
    12
    +class Engine;
    
    13
    +class CharMapComboBox
    
    14
    +: public QComboBox
    
    15
    +{
    
    16
    +  Q_OBJECT
    
    17
    +public:
    
    18
    +  CharMapComboBox(QWidget* parent, Engine* engine, bool haveGlyphOrder = true);
    
    19
    +  ~CharMapComboBox() override = default;
    
    20
    +
    
    21
    +  bool haveGlyphOrder_;
    
    22
    +
    
    23
    +  std::vector<CharMapInfo>& charMaps() { return charMaps_; }
    
    24
    +  int currentCharMapIndex();
    
    25
    +  int defaultFirstGlyphIndex();
    
    26
    +  void repopulate();
    
    27
    +  void repopulate(std::vector<CharMapInfo>& charMaps);
    
    28
    +
    
    29
    +signals:
    
    30
    +  void forceUpdateLimitIndex();
    
    31
    +
    
    32
    +private:
    
    33
    +  Engine* engine_;
    
    34
    +  std::vector<CharMapInfo> charMaps_;
    
    35
    +
    
    36
    +  void updateToolTip();
    
    37
    +};
    
    38
    +
    
    39
    +
    
    40
    +// charmapcombobox.hpp


  • reply via email to

    [Prev in Thread] Current Thread [Next in Thread]