freetype-commit
[Top][All Lists]
Advanced

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

[freetype2-demos] master 9f70ceb 20/41: [ftinspect] Support MM/GX.


From: Werner Lemberg
Subject: [freetype2-demos] master 9f70ceb 20/41: [ftinspect] Support MM/GX.
Date: Mon, 3 Oct 2022 11:27:02 -0400 (EDT)

branch: master
commit 9f70ceb30e40f897575ee6008a33bafa617b0043
Author: Charlie Jiang <w@chariri.moe>
Commit: Werner Lemberg <wl@gnu.org>

    [ftinspect] Support MM/GX.
    
    * src/ftinspect/panels/settingpanelmmgx.cpp,
      src/ftinspect/panels/settingpanelmmgx.hpp:
      Add GUI widgets for MM/GX settings.
    
    * src/ftinspect/engine/mmgx.cpp, src/ftinspect/engine/mmgx.hpp:
      Add `MMGXAxisInfo` to retrieve axes info. However, the SFNT name table
      isn't implemented, so the SFNT based axis names are unavailable.
    
    * src/ftinspect/engine/engine.cpp, src/ftinspect/engine/engine.hpp:
      Add `applyMMGXDesignCoords`. Add getters `currentFontMMGXState` and
      `currentFontMMGXAxes`. Info is retrieved when loading font.
    
    * src/ftinspect/panels/settingpanel.cpp,
      src/ftinspect/panels/settingpanel.hpp: Uncomment functional code.
      Remove `checkHintingMode` and `checkStemDarkening`. Applying of delayed
      settings is taken care by the `MainGUI`. So emitting `fontReloadNeeded`
      simply will do the work.
    
    * src/ftinspect/maingui.cpp: Don't reset the cache so aggressively.
    
    * src/ftinspect/CMakeLists.txt, src/ftinspect/meson.build: Updated.
---
 src/ftinspect/CMakeLists.txt              |   2 +
 src/ftinspect/engine/engine.cpp           |  15 ++
 src/ftinspect/engine/engine.hpp           |  10 +-
 src/ftinspect/engine/mmgx.cpp             |  83 +++++++
 src/ftinspect/engine/mmgx.hpp             |  57 +++++
 src/ftinspect/maingui.cpp                 |   3 +-
 src/ftinspect/meson.build                 |   3 +
 src/ftinspect/panels/settingpanel.cpp     |  34 +--
 src/ftinspect/panels/settingpanel.hpp     |   6 +-
 src/ftinspect/panels/settingpanelmmgx.cpp | 359 ++++++++++++++++++++++++++++++
 src/ftinspect/panels/settingpanelmmgx.hpp | 109 +++++++++
 11 files changed, 647 insertions(+), 34 deletions(-)

diff --git a/src/ftinspect/CMakeLists.txt b/src/ftinspect/CMakeLists.txt
index a8eaea6..bbb9112 100644
--- a/src/ftinspect/CMakeLists.txt
+++ b/src/ftinspect/CMakeLists.txt
@@ -24,6 +24,7 @@ add_executable(ftinspect
   "engine/fontfilemanager.cpp"
   "engine/rendering.cpp"
   "engine/paletteinfo.cpp"
+  "engine/mmgx.cpp"
 
   "glyphcomponents/glyphbitmap.cpp"
   "glyphcomponents/glyphoutline.cpp"
@@ -40,6 +41,7 @@ add_executable(ftinspect
   "models/customcomboboxmodels.cpp"
 
   "panels/settingpanel.cpp"
+  "panels/settingpanelmmgx.cpp"
   "panels/singular.cpp"
 )
 target_link_libraries(ftinspect
diff --git a/src/ftinspect/engine/engine.cpp b/src/ftinspect/engine/engine.cpp
index 9f96109..9cf30b4 100644
--- a/src/ftinspect/engine/engine.cpp
+++ b/src/ftinspect/engine/engine.cpp
@@ -11,6 +11,7 @@
 #include <freetype/ftmodapi.h>
 #include <freetype/ftdriver.h>
 #include <freetype/ftlcdfil.h>
+#include <freetype/ftmm.h>
 
 
 /////////////////////////////////////////////////////////////////////////////
@@ -344,6 +345,7 @@ Engine::loadFont(int fontIndex,
     else
       fontType_ = FontType_Other;
     loadPaletteInfos();
+    curMMGXState_ = MMGXAxisInfo::get(this, curMMGXAxes_);
   }
 
   curNumGlyphs_ = numGlyphs;
@@ -658,6 +660,19 @@ Engine::setStemDarkening(bool darkening)
 }
 
 
+void
+Engine::applyMMGXDesignCoords(FT_Fixed* coords,
+                              size_t count)
+{
+  if (!ftSize_)
+    return;
+  if (count >= UINT_MAX)
+    count = UINT_MAX - 1;
+  FT_Set_Var_Design_Coordinates(ftSize_->face,
+                                static_cast<unsigned>(count), coords);
+}
+
+
 void
 Engine::update()
 {
diff --git a/src/ftinspect/engine/engine.hpp b/src/ftinspect/engine/engine.hpp
index 935a06b..7b264d7 100644
--- a/src/ftinspect/engine/engine.hpp
+++ b/src/ftinspect/engine/engine.hpp
@@ -8,6 +8,7 @@
 #include "fontfilemanager.hpp"
 
 #include "paletteinfo.hpp"
+#include "mmgx.hpp"
 #include "rendering.hpp"
 
 #include <memory>
@@ -119,6 +120,8 @@ public:
   std::vector<PaletteInfo>& currentFontPalettes() { return curPaletteInfos_; }
   FT_Color* currentPalette() { return palette_; }
   FT_Palette_Data& currentFontPaletteData() { return paletteData_; }
+  MMGXState currentFontMMGXState() { return curMMGXState_; }
+  std::vector<MMGXAxisInfo>& currentFontMMGXAxes() { return curMMGXAxes_; }
 
   QString glyphName(int glyphIndex);
   long numberOfFaces(int fontIndex);
@@ -183,7 +186,8 @@ public:
   void setTTInterpreterVersion(int version);
 
   void setStemDarkening(bool darkening);
-  
+  void applyMMGXDesignCoords(FT_Fixed* coords, size_t count);
+
   //////// Misc
 
   friend FT_Error faceRequester(FTC_FaceID,
@@ -200,11 +204,13 @@ private:
 
   // font info
   int curFontIndex_ = -1;
+  int fontType_;
   QString curFamilyName_;
   QString curStyleName_;
   int curNumGlyphs_ = -1;
   std::vector<PaletteInfo> curPaletteInfos_;
-  int fontType_;
+  MMGXState curMMGXState_ = MMGXState::NoMMGX;
+  std::vector<MMGXAxisInfo> curMMGXAxes_;
 
   // basic objects
   FT_Library library_;
diff --git a/src/ftinspect/engine/mmgx.cpp b/src/ftinspect/engine/mmgx.cpp
new file mode 100644
index 0000000..b978272
--- /dev/null
+++ b/src/ftinspect/engine/mmgx.cpp
@@ -0,0 +1,83 @@
+// mmgx.cpp
+
+// Copyright (C) 2022 by Charlie Jiang.
+
+#include "mmgx.hpp"
+
+#include "engine.hpp"
+
+#include <freetype/ftmm.h>
+
+
+MMGXState
+MMGXAxisInfo::get(Engine* engine,
+                  std::vector<MMGXAxisInfo>& infos)
+{
+  auto face = engine->currentFallbackFtFace();
+  if (!face)
+  {
+    infos.clear();
+    return MMGXState::NoMMGX;
+  }
+
+  if (!FT_HAS_MULTIPLE_MASTERS(face))
+  {
+    infos.clear();
+    return MMGXState::NoMMGX;
+  }
+
+  FT_Multi_Master dummy;
+  auto error = FT_Get_Multi_Master(face, &dummy);
+  auto state = error ? MMGXState::GX_OVF : MMGXState::MM;
+  
+  FT_MM_Var* mm;
+  if (FT_Get_MM_Var(face, &mm))
+  {
+    infos.clear();
+    return state;
+  }
+
+  infos.resize(mm->num_axis);
+
+  //auto& sfnt = engine->currentFontSFNTNames();
+  for (unsigned int i = 0; i < mm->num_axis; ++i)
+  {
+    auto& axis = mm->axis[i];
+    auto& info = infos[i];
+    info.maximum = axis.maximum / 65536.0;
+    info.minimum = axis.minimum / 65536.0;
+    info.def = axis.def / 65536.0;
+    info.tag = axis.tag;
+    info.isMM = state == MMGXState::MM;
+
+    unsigned int flags = 0;
+    FT_Get_Var_Axis_Flags(mm, i, &flags);
+    info.hidden = (flags & FT_VAR_AXIS_FLAG_HIDDEN) != 0;
+
+    auto nameSet = false;
+    if (state == MMGXState::GX_OVF)
+    {
+      auto strid = mm->axis[i].strid;
+      //for (auto& obj : sfnt)
+      //{
+      //  if (obj.nameID == strid && obj.strValid)
+      //  {
+      //    info.name = obj.str;
+      //    nameSet = true;
+      //    break;
+      //  }
+      //}
+    }
+
+    // XXX security flaw
+    if (!nameSet)
+      info.name = QString(axis.name);
+  }
+
+  FT_Done_MM_Var(face->glyph->library, mm);
+
+  return state;
+}
+
+
+// end of mmgx.cpp
diff --git a/src/ftinspect/engine/mmgx.hpp b/src/ftinspect/engine/mmgx.hpp
new file mode 100644
index 0000000..6d5a3b0
--- /dev/null
+++ b/src/ftinspect/engine/mmgx.hpp
@@ -0,0 +1,57 @@
+// mmgx.hpp
+
+// Copyright (C) 2022 by Charlie Jiang.
+
+#pragma once
+
+#include <vector>
+#include <QString>
+
+class Engine;
+
+enum class MMGXState
+{
+  NoMMGX,
+  MM,     // Adobe MM
+  GX_OVF, // GX or OpenType variable fonts
+};
+
+struct MMGXAxisInfo
+{
+  QString name;
+  unsigned long tag;
+
+  double minimum;
+  double maximum;
+  double def;
+
+  bool hidden;
+  bool isMM;
+
+  static MMGXState get(Engine* engine, std::vector<MMGXAxisInfo>& infos);
+
+
+  friend bool
+  operator==(const MMGXAxisInfo& lhs,
+             const MMGXAxisInfo& rhs)
+  {
+    return lhs.name == rhs.name
+      && lhs.tag == rhs.tag
+      && lhs.minimum == rhs.minimum
+      && lhs.maximum == rhs.maximum
+      && lhs.def == rhs.def
+      && lhs.hidden == rhs.hidden
+      && lhs.isMM == rhs.isMM;
+  }
+
+
+  friend bool
+  operator!=(const MMGXAxisInfo& lhs,
+             const MMGXAxisInfo& rhs)
+  {
+    return !(lhs == rhs);
+  }
+};
+
+
+// end of mmgx.hpp
diff --git a/src/ftinspect/maingui.cpp b/src/ftinspect/maingui.cpp
index da3bada..9fc4c01 100644
--- a/src/ftinspect/maingui.cpp
+++ b/src/ftinspect/maingui.cpp
@@ -180,7 +180,7 @@ MainGUI::repaintCurrentTab()
 void
 MainGUI::reloadCurrentTabFont()
 {
-  engine_->resetCache();
+  settingPanel_->applyDelayedSettings(); // This will reset the cache.
   applySettings();
   auto index = tabWidget_->currentIndex();
   if (index >= 0 && static_cast<size_t>(index) < tabs_.size())
@@ -192,7 +192,6 @@ void
 MainGUI::applySettings()
 {
   settingPanel_->applySettings();
-  settingPanel_->applyDelayedSettings();
 }
 
 
diff --git a/src/ftinspect/meson.build b/src/ftinspect/meson.build
index 05c5854..71450ab 100644
--- a/src/ftinspect/meson.build
+++ b/src/ftinspect/meson.build
@@ -25,6 +25,7 @@ if qt5_dep.found()
     'engine/fontfilemanager.cpp',
     'engine/rendering.cpp',
     'engine/paletteinfo.cpp',
+    'engine/mmgx.cpp',
 
     'glyphcomponents/glyphbitmap.cpp',
     'glyphcomponents/glyphoutline.cpp',
@@ -41,6 +42,7 @@ if qt5_dep.found()
     'models/customcomboboxmodels.cpp',
 
     'panels/settingpanel.cpp',
+    'panels/settingpanelmmgx.cpp',
     'panels/singular.cpp',
 
     'ftinspect.cpp',
@@ -58,6 +60,7 @@ if qt5_dep.found()
       'maingui.hpp',
       'models/customcomboboxmodels.hpp',
       'panels/settingpanel.hpp',
+      'panels/settingpanelmmgx.hpp',
       'panels/singular.hpp',
     ],
     dependencies: qt5_dep)
diff --git a/src/ftinspect/panels/settingpanel.cpp 
b/src/ftinspect/panels/settingpanel.cpp
index 359410e..33345f7 100644
--- a/src/ftinspect/panels/settingpanel.cpp
+++ b/src/ftinspect/panels/settingpanel.cpp
@@ -99,16 +99,6 @@ SettingPanel::checkHinting()
 }
 
 
-void
-SettingPanel::checkHintingMode()
-{
-  //if (!comparatorMode_)
-  applyDelayedSettings();
-
-  emit fontReloadNeeded();
-}
-
-
 void
 SettingPanel::checkAutoHinting()
 {
@@ -193,16 +183,6 @@ SettingPanel::checkPalette()
 }
 
 
-void
-SettingPanel::checkStemDarkening()
-{
-  //if (!comparatorMode_)
-  applyDelayedSettings();
-
-  emit fontReloadNeeded();
-}
-
-
 void
 SettingPanel::openBackgroundPicker()
 {
@@ -339,7 +319,7 @@ SettingPanel::onFontChanged()
   paletteComboBox_->setEnabled(colorLayerCheckBox_->isChecked()
                                && paletteComboBox_->count() > 0);
   populatePalettes();
-  //mmgxPanel_->reloadFont();
+  mmgxPanel_->reloadFont();
   blockSignals(blockState);
 
   // Place this after `blockSignals` to let the signals emitted normally
@@ -389,7 +369,7 @@ SettingPanel::applySettings()
 
   engine_->renderingEngine()->setForeground(foregroundColor_.rgba());
   engine_->renderingEngine()->setBackground(backgroundColor_.rgba());
-  //mmgxPanel_->applySettings();
+  mmgxPanel_->applySettings();
 }
 
 
@@ -481,8 +461,7 @@ SettingPanel::createLayout()
   gammaLabel_->setBuddy(gammaSlider_);
   gammaValueLabel_ = new QLabel(this);
 
-  // TODO: MM/GX
-  mmgxPanel_ = new QWidget(this);
+  mmgxPanel_ = new SettingPanelMMGX(this, engine_);
 
   backgroundButton_ = new QPushButton(tr("Background"), this);
   foregroundButton_ = new QPushButton(tr("Foreground"), this);
@@ -622,7 +601,7 @@ SettingPanel::createConnections()
   // use `qOverload` here to prevent ambiguity.
   connect(hintingModeComboBox_, 
           QOverload<int>::of(&QComboBox::currentIndexChanged),
-          this, &SettingPanel::checkHintingMode);
+          this, &SettingPanel::fontReloadNeeded);
   connect(antiAliasingComboBox_,
           QOverload<int>::of(&QComboBox::currentIndexChanged),
           this, &SettingPanel::checkAntiAliasing);
@@ -656,7 +635,7 @@ SettingPanel::createConnections()
   connect(embeddedBitmapCheckBox_, &QCheckBox::clicked,
           this, &SettingPanel::fontReloadNeeded);
   connect(stemDarkeningCheckBox_, &QCheckBox::clicked,
-          this, &SettingPanel::checkStemDarkening);
+          this, &SettingPanel::fontReloadNeeded);
   connect(colorLayerCheckBox_, &QCheckBox::clicked,
           this, &SettingPanel::checkPalette);
 
@@ -664,6 +643,9 @@ SettingPanel::createConnections()
           this, &SettingPanel::openBackgroundPicker);
   connect(foregroundButton_, &QPushButton::clicked,
           this, &SettingPanel::openForegroundPicker);
+
+  connect(mmgxPanel_, &SettingPanelMMGX::mmgxCoordsChanged,
+          this, &SettingPanel::fontReloadNeeded);
 }
 
 
diff --git a/src/ftinspect/panels/settingpanel.hpp 
b/src/ftinspect/panels/settingpanel.hpp
index 73c309a..418958e 100644
--- a/src/ftinspect/panels/settingpanel.hpp
+++ b/src/ftinspect/panels/settingpanel.hpp
@@ -6,6 +6,7 @@
 
 #include "../engine/engine.hpp"
 #include "../models/customcomboboxmodels.hpp"
+#include "settingpanelmmgx.hpp"
 
 #include <QWidget>
 #include <QTabWidget>
@@ -54,8 +55,7 @@ private:
 
   QWidget* generalTab_;
   QWidget* hintingRenderingTab_;
-  //SettingPanelMMGX* mmgxPanel_;
-  QWidget* mmgxPanel_;
+  SettingPanelMMGX* mmgxPanel_;
 
   QLabel* gammaLabel_;
   QLabel* gammaValueLabel_;
@@ -113,11 +113,9 @@ private:
 
   void checkAllSettings();
   void checkHinting();
-  void checkHintingMode();
   void checkAutoHinting();
   void checkAntiAliasing();
   void checkPalette();
-  void checkStemDarkening();
 
   void openBackgroundPicker();
   void openForegroundPicker();
diff --git a/src/ftinspect/panels/settingpanelmmgx.cpp 
b/src/ftinspect/panels/settingpanelmmgx.cpp
new file mode 100644
index 0000000..f80c318
--- /dev/null
+++ b/src/ftinspect/panels/settingpanelmmgx.cpp
@@ -0,0 +1,359 @@
+// settingpanelmmgx.cpp
+
+// Copyright (C) 2022 by Charlie Jiang.
+
+#include "settingpanelmmgx.hpp"
+
+#include <QScrollBar>
+
+#include "../engine/engine.hpp"
+#include "../uihelper.hpp"
+
+SettingPanelMMGX::SettingPanelMMGX(QWidget* parent,
+                                   Engine* engine)
+: QWidget(parent),
+  engine_(engine)
+{
+  createLayout();
+  createConnections();
+}
+
+
+void
+SettingPanelMMGX::reloadFont()
+{
+  setEnabled(engine_->currentFontMMGXState() != MMGXState::NoMMGX);
+  if (currentAxes_ == engine_->currentFontMMGXAxes())
+    return;
+
+  currentAxes_ = engine_->currentFontMMGXAxes();
+
+  auto newSize = currentAxes_.size();
+  auto oldSize = itemWidgets_.size();
+  auto minSize = std::min(newSize, oldSize);
+
+  // This won't trigger unexpected updating since signals are blocked
+  for (size_t i = 0; i < minSize; ++i)
+    itemWidgets_[i]->updateInfo(currentAxes_[i]);
+
+  if (newSize < oldSize)
+  {
+    for (size_t i = newSize; i < oldSize; ++i)
+    {
+      auto w = itemWidgets_[i];
+      disconnect(w);
+      listLayout_->removeWidget(w);
+      delete w;
+    }
+    itemWidgets_.resize(newSize);
+  }
+  else if (newSize > oldSize)
+  {
+    itemWidgets_.resize(newSize);
+    for (size_t i = oldSize; i < newSize; ++i)
+    {
+      auto w = new MMGXSettingItem(this);
+      itemWidgets_[i] = w;
+      w->updateInfo(currentAxes_[i]);
+      listLayout_->addWidget(w);
+      connect(w, &MMGXSettingItem::valueChanged,
+              [this, i] { itemChanged(i); });
+    }
+  }
+  checkHidden();
+  retrieveValues();
+  applySettings();
+}
+
+
+void
+SettingPanelMMGX::applySettings()
+{
+  engine_->reloadFont();
+  engine_->applyMMGXDesignCoords(currentValues_.data(),
+                                 currentValues_.size());
+}
+
+
+void
+SettingPanelMMGX::checkHidden()
+{
+  if (itemWidgets_.size() < currentAxes_.size())
+    return; // This should never happen!
+  for (int i = 0; static_cast<size_t>(i) < currentAxes_.size(); ++i)
+    itemWidgets_[i]->setVisible(showHiddenCheckBox_->isChecked()
+                                || !currentAxes_[i].hidden);
+}
+
+
+void
+SettingPanelMMGX::createLayout()
+{
+  showHiddenCheckBox_ = new QCheckBox(tr("Show Hidden"), this);
+  groupingCheckBox_ = new QCheckBox(tr("Grouping"), this);
+  resetDefaultButton_ = new QPushButton(tr("Reset Default"), this);
+  itemsListWidget_ = new QWidget(this);
+  scrollArea_ = new UnboundScrollArea(this);
+
+  scrollArea_->setWidget(itemsListWidget_);
+  scrollArea_->setWidgetResizable(true);
+  itemsListWidget_->setAutoFillBackground(false);
+
+  mainLayout_ = new QVBoxLayout;
+  listLayout_ = new QVBoxLayout;
+  listWrapperLayout_ = new QVBoxLayout;
+
+  listLayout_->setSpacing(0);
+  listLayout_->setContentsMargins(0, 0, 0, 0);
+  itemsListWidget_->setContentsMargins(0, 0, 0, 0);
+
+  itemsListWidget_->setLayout(listWrapperLayout_);
+
+  listWrapperLayout_->addLayout(listLayout_);
+  listWrapperLayout_->addStretch(1);
+
+  mainLayout_->addWidget(showHiddenCheckBox_);
+  mainLayout_->addWidget(groupingCheckBox_);
+  mainLayout_->addWidget(resetDefaultButton_);
+  mainLayout_->addWidget(scrollArea_, 1);
+
+  setLayout(mainLayout_);
+}
+
+
+void
+SettingPanelMMGX::createConnections()
+{
+  connect(showHiddenCheckBox_, &QCheckBox::clicked,
+          this, &SettingPanelMMGX::checkHidden);
+  connect(resetDefaultButton_, &QCheckBox::clicked,
+          this, &SettingPanelMMGX::resetDefaultClicked);
+  connect(groupingCheckBox_, &QCheckBox::clicked,
+          this, &SettingPanelMMGX::checkGrouping);
+}
+
+
+void
+SettingPanelMMGX::retrieveValues()
+{
+  currentValues_.resize(currentAxes_.size());
+  for (unsigned i = 0; i < currentAxes_.size(); ++i)
+    currentValues_[i] = itemWidgets_[i]->value();
+}
+
+
+void
+SettingPanelMMGX::itemChanged(size_t index)
+{
+  if (groupingCheckBox_->isChecked()
+      && index < currentAxes_.size() && index < itemWidgets_.size())
+  {
+    auto tag = currentAxes_[index].tag;
+    auto value = itemWidgets_[index]->value();
+    for (size_t i = 0; i < itemWidgets_.size(); ++i)
+      if (i != index && currentAxes_[i].tag == tag)
+        itemWidgets_[i]->setValue(value);
+  }
+
+  retrieveValues();
+  emit mmgxCoordsChanged();
+}
+
+
+void
+SettingPanelMMGX::resetDefaultClicked()
+{
+  for (auto w : itemWidgets_)
+    w->resetDefault();
+
+  retrieveValues();
+  emit mmgxCoordsChanged();
+}
+
+
+void
+SettingPanelMMGX::checkGrouping()
+{
+  if (!groupingCheckBox_->isChecked())
+    return;
+  auto maxIndex = std::max(itemWidgets_.size(), currentAxes_.size());
+  for (size_t i = maxIndex - 1; ; --i)
+  {
+    if (!currentAxes_[i].hidden)
+    {
+      auto tag = currentAxes_[i].tag;
+      auto value = itemWidgets_[i]->value();
+      for (size_t j = 0; j < maxIndex; ++j)
+        if (j != i && currentAxes_[j].tag == tag)
+          itemWidgets_[j]->setValue(value);
+    }
+
+    if (i == 0)
+      break;
+  }
+
+  retrieveValues();
+  emit mmgxCoordsChanged();
+}
+
+
+MMGXSettingItem::MMGXSettingItem(QWidget* parent)
+: QFrame(parent)
+{
+  createLayout();
+  createConnections();
+}
+
+
+void
+MMGXSettingItem::updateInfo(MMGXAxisInfo& info)
+{
+  axisInfo_ = info;
+  valueValidator_->setRange(info.minimum, info.maximum, 10);
+
+  if (info.hidden)
+    nameLabel_->setText("<i>" + info.name + "</i>");
+  else
+    nameLabel_->setText(info.name);
+
+  // To keep things simple, we will use 1/1024 of the span between the min and
+  // max as one step on the slider.
+
+  slider_->setMinimum(0);
+  slider_->setTickInterval(1);
+  slider_->setMaximum(1024);
+
+  resetDefault();
+}
+
+
+void
+MMGXSettingItem::setValue(FT_Fixed value)
+{
+  actualValue_ = value;
+  updateSlider();
+  updateLineEdit();
+}
+
+
+void
+MMGXSettingItem::resetDefault()
+{
+  setValue(static_cast<FT_Fixed>(axisInfo_.def * 65536.0));
+}
+
+
+void
+MMGXSettingItem::createLayout()
+{
+  nameLabel_ = new QLabel(this);
+
+  // 1/1024 = 0.0009765625
+  valueValidator_ = new QDoubleValidator(0, 0, 10, this);
+  valueValidator_->setNotation(QDoubleValidator::StandardNotation);
+
+  valueLineEdit_ = new QLineEdit("0", this);
+  valueLineEdit_->setValidator(valueValidator_);
+
+  slider_ = new QSlider(this);
+  slider_->setOrientation(Qt::Horizontal);
+
+  resetDefaultButton_ = new QPushButton(this);
+  resetDefaultButton_->setText(tr("Def"));
+  setButtonNarrowest(resetDefaultButton_);
+
+  nameLabel_->setToolTip(tr("Axis name or tag"));
+  slider_->setToolTip(
+    tr("Axis value (max precision: 1/1024 of the value range)"));
+  valueLineEdit_->setToolTip(tr("Axis value"));
+  resetDefaultButton_->setToolTip(tr("Reset axis to default"));
+
+  mainLayout_ = new QGridLayout();
+
+  mainLayout_->addWidget(nameLabel_, 0, 0, 1, 2);
+  mainLayout_->addWidget(slider_, 1, 0, 1, 2);
+  mainLayout_->addWidget(valueLineEdit_, 2, 0, 1, 1, Qt::AlignVCenter);
+  mainLayout_->addWidget(resetDefaultButton_, 2, 1, 1, 1, Qt::AlignVCenter);
+
+  mainLayout_->setSpacing(4);
+  mainLayout_->setContentsMargins(4, 4, 4, 4);
+  setContentsMargins(0, 0, 0, 0);
+  setLayout(mainLayout_);
+  setFrameShape(StyledPanel);
+}
+
+
+void
+MMGXSettingItem::createConnections()
+{
+  connect(slider_, &QSlider::valueChanged,
+          this, &MMGXSettingItem::sliderValueChanged);
+  connect(valueLineEdit_, &QLineEdit::editingFinished,
+          this, &MMGXSettingItem::lineEditChanged);
+  connect(resetDefaultButton_, &QToolButton::clicked,
+          this, &MMGXSettingItem::resetDefaultSingle);
+}
+
+
+void
+MMGXSettingItem::sliderValueChanged()
+{
+  auto value = slider_->value() / 1024.0
+               * (axisInfo_.maximum - axisInfo_.minimum)
+               + axisInfo_.minimum;
+  actualValue_ = static_cast<FT_Fixed>(value * 65536.0);
+
+  if (axisInfo_.isMM)
+    actualValue_ = FT_RoundFix(actualValue_);
+
+  updateLineEdit();
+  emit valueChanged();
+}
+
+
+void
+MMGXSettingItem::lineEditChanged()
+{
+  bool succ = false;
+  auto newValue = valueLineEdit_->text().toDouble(&succ);
+  if (!succ || newValue > axisInfo_.maximum || newValue < axisInfo_.minimum)
+  {
+    updateLineEdit();
+    return;
+  }
+
+  actualValue_ = static_cast<FT_Fixed>(newValue / 65536.0);
+
+  updateSlider();
+  emit valueChanged();
+}
+
+
+void
+MMGXSettingItem::updateLineEdit()
+{
+  QSignalBlocker blocker(valueLineEdit_);
+  valueLineEdit_->setText(QString::number(actualValue_ / 65536.0));
+}
+
+
+void
+MMGXSettingItem::updateSlider()
+{
+  QSignalBlocker blocker(slider_);
+  slider_->setValue(
+    static_cast<int>((actualValue_ / 65536.0 - axisInfo_.minimum)
+                     / (axisInfo_.maximum - axisInfo_.minimum)
+                     * 1024));
+}
+
+
+void
+MMGXSettingItem::resetDefaultSingle()
+{
+  resetDefault();
+  emit valueChanged();
+}
+
+
+// end of settingpanelmmgx.cpp
diff --git a/src/ftinspect/panels/settingpanelmmgx.hpp 
b/src/ftinspect/panels/settingpanelmmgx.hpp
new file mode 100644
index 0000000..f61ca42
--- /dev/null
+++ b/src/ftinspect/panels/settingpanelmmgx.hpp
@@ -0,0 +1,109 @@
+// settingpanelmmgx.hpp
+
+// Copyright (C) 2022 by Charlie Jiang.
+
+#pragma once
+
+#include "../engine/mmgx.hpp"
+#include "../widgets/customwidgets.hpp"
+
+#include <QWidget>
+#include <QGridLayout>
+#include <QBoxLayout>
+#include <QCheckBox>
+#include <QScrollArea>
+#include <QLineEdit>
+#include <QSlider>
+#include <QFrame>
+#include <QPushButton>
+#include <QToolButton>
+#include <QLabel>
+#include <QDoubleValidator>
+
+#include <freetype/fttypes.h>
+
+class Engine;
+
+class MMGXSettingItem;
+class SettingPanelMMGX
+: public QWidget
+{
+  Q_OBJECT
+public:
+  SettingPanelMMGX(QWidget* parent, Engine* engine);
+  ~SettingPanelMMGX() override = default;
+
+  void reloadFont();
+  void applySettings();
+  void checkHidden();
+  std::vector<FT_Fixed>& mmgxCoords() { return currentValues_; }
+
+signals:
+  void mmgxCoordsChanged();
+
+private:
+  Engine* engine_;
+
+  QCheckBox* showHiddenCheckBox_;
+  QCheckBox* groupingCheckBox_;
+  QPushButton* resetDefaultButton_;
+  QWidget* itemsListWidget_;
+  UnboundScrollArea* scrollArea_;
+  std::vector<MMGXSettingItem*> itemWidgets_;
+
+  QVBoxLayout* mainLayout_;
+  QVBoxLayout* listLayout_;
+  QVBoxLayout* listWrapperLayout_;
+
+  std::vector<FT_Fixed> currentValues_;
+  std::vector<MMGXAxisInfo> currentAxes_;
+
+  void createLayout();
+  void createConnections();
+
+  void retrieveValues();
+  void itemChanged(size_t index);
+  void resetDefaultClicked();
+  void checkGrouping();
+};
+
+
+class MMGXSettingItem
+: public QFrame
+{
+  Q_OBJECT
+public:
+  MMGXSettingItem(QWidget* parent);
+  ~MMGXSettingItem() override = default;
+
+  void updateInfo(MMGXAxisInfo& info);
+  FT_Fixed value() { return actualValue_; }
+  void setValue(FT_Fixed value);
+  void resetDefault();
+
+signals:
+  void valueChanged();
+
+private:
+  QLabel* nameLabel_;
+  QSlider* slider_;
+  QPushButton* resetDefaultButton_;
+  QLineEdit* valueLineEdit_;
+  QDoubleValidator* valueValidator_;
+
+  QGridLayout* mainLayout_;
+
+  FT_Fixed actualValue_;
+  MMGXAxisInfo axisInfo_;
+
+  void createLayout();
+  void createConnections();
+  void sliderValueChanged();
+  void lineEditChanged();
+  void updateLineEdit();
+  void updateSlider();
+  void resetDefaultSingle();
+};
+
+
+// end of settingpanelmmgx.hpp



reply via email to

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