Blob Blame Raw


// character_manager.cpp: implementation of the TFont class.
//
//////////////////////////////////////////////////////////////////////

#include "tpixelgr.h"
#include "tfont.h"
#include "tstroke.h"
//#include "tcurves.h"
#include "traster.h"
#include <vector>
#include <iostream>
#include <string>
//#include <tstring.h>
#include <tmathutil.h>
//#include <tdebugmessage.h>
#include "tvectorimage.h"
using namespace std;

//=============================================================================

typedef map<wstring, LOGFONTW> WindowsFontTable;
typedef map<pair<unsigned short, unsigned short>, int> KerningPairs;

//=============================================================================

struct TFont::Impl {
  bool m_hasKerning;
  bool m_hasVertical;
  HFONT m_font;
  HDC m_hdc;
  TEXTMETRICW m_metrics;
  KerningPairs m_kerningPairs;

  Impl(const LOGFONTW &font, HDC hdc);
  ~Impl();
};

//-----------------------------------------------------------------------------

TFont::TFont(const LOGFONTW &font, HDC hdc) { m_pimpl = new Impl(font, hdc); }

//-----------------------------------------------------------------------------

TFont::~TFont() { delete m_pimpl; }

//-----------------------------------------------------------------------------

TFont::Impl::Impl(const LOGFONTW &logfont, HDC hdc) : m_hdc(hdc) {
  m_font = CreateFontIndirectW(&logfont);

  if (!m_font) throw TFontCreationError();

  HGDIOBJ hObj = SelectObject(hdc, m_font);
  if (!hObj || hObj == HGDI_ERROR) throw TFontCreationError();

  if (!GetTextMetricsW(hdc, &m_metrics)) throw TFontCreationError();

  DWORD pairsCount = GetKerningPairsW(hdc, 0, 0);
  if (pairsCount) {
    m_hasKerning = true;
    std::unique_ptr<KERNINGPAIR[]> tempKernPairs(new KERNINGPAIR[pairsCount]);
    GetKerningPairsW(hdc, pairsCount, tempKernPairs.get());
    for (UINT i = 0; i < pairsCount; i++) {
      pair<unsigned short, unsigned short> key =
          make_pair(tempKernPairs[i].wFirst, tempKernPairs[i].wSecond);
      m_kerningPairs[key] = tempKernPairs[i].iKernAmount;
    }
  } else
    m_hasKerning = false;

  m_hasVertical = (logfont.lfFaceName)[0] == '@';
}

//-----------------------------------------------------------------------------

TFont::Impl::~Impl() {
  // delete m_advances;
  DeleteObject(m_font);
}

//-----------------------------------------------------------------------------

namespace {
inline TThickPoint toThickPoint(POINTFX point) {
  double app1 = point.x.value +
                ((double)point.x.fract) / (std::numeric_limits<WORD>::max)();
  double app2 = point.y.value +
                ((double)point.y.fract) / (std::numeric_limits<WORD>::max)();
  return TThickPoint(app1, app2, 0);
}
}

//-----------------------------------------------------------------------------

TPoint TFont::drawChar(TVectorImageP &image, wchar_t charcode,
                       wchar_t nextCharCode) const

{
  GLYPHMETRICS gm;
  MAT2 mat2;
  mat2.eM11.fract = 0;
  mat2.eM12.fract = 0;
  mat2.eM21.fract = 0;
  mat2.eM22.fract = 0;
  mat2.eM11.value = 1;
  mat2.eM12.value = 0;
  mat2.eM21.value = 0;
  mat2.eM22.value = 1;

  vector<TThickPoint> points;

  UINT j = 0;

  DWORD charMemorySize =
      GetGlyphOutlineW(m_pimpl->m_hdc, charcode, GGO_NATIVE, &gm, 0, 0, &mat2);
  if (charMemorySize == GDI_ERROR) {
    assert(0);
    return TPoint();
  }

  std::unique_ptr<char[]> lpvBuffer(new char[charMemorySize]);

  charMemorySize = GetGlyphOutlineW(m_pimpl->m_hdc, charcode, GGO_NATIVE, &gm,
                                    charMemorySize, lpvBuffer.get(), &mat2);
  if (charMemorySize == GDI_ERROR) {
    assert(0);
    return TPoint();
  }

  TTPOLYGONHEADER *header = (TTPOLYGONHEADER *)lpvBuffer.get();

  while ((char *)header < (char *)lpvBuffer.get() + charMemorySize) {
    points.clear();
    TThickPoint startPoint = toThickPoint(header->pfxStart);
    points.push_back(startPoint);

    if (header->dwType != TT_POLYGON_TYPE) {
      assert(0);
    }
    int memorySize = header->cb;

    TTPOLYCURVE *curve = (TTPOLYCURVE *)(header + 1);

    while ((char *)curve < (char *)header + memorySize) {
      switch (curve->wType) {
      case TT_PRIM_LINE:

        for (j = 0; j < curve->cpfx; j++) {
          TThickPoint p0 = points.back();
          TThickPoint p1 = toThickPoint(((*curve).apfx[j]));
          points.push_back((p0 + p1) * 0.5);
          points.push_back(p1);
        }

        break;

      case TT_PRIM_QSPLINE:

        for (j = 0; (int)j + 2 < curve->cpfx; j++) {
          TThickPoint p1 = toThickPoint(((*curve).apfx[j]));
          TThickPoint p2 = toThickPoint(((*curve).apfx[j + 1]));

          points.push_back(p1);
          points.push_back((p1 + p2) * 0.5);
        }
        points.push_back(toThickPoint(((*curve).apfx[j++])));
        points.push_back(toThickPoint(((*curve).apfx[j++])));

        break;
      case TT_PRIM_CSPLINE:
        assert(0);
        break;
      default:
        assert(0);
      }

      curve = (TTPOLYCURVE *)(&(curve->apfx)[j]);
    }

    TThickPoint p0 = points.back();
    if (!isAlmostZero(p0.x - startPoint.x) ||
        !isAlmostZero(p0.y - startPoint.y)) {
      points.push_back((p0 + startPoint) * 0.5);
      points.push_back(startPoint);
    }

    TStroke *stroke = new TStroke();
    stroke->reshape(&(points[0]), points.size());
    stroke->setSelfLoop(true);
    image->addStroke(stroke);

    header = (TTPOLYGONHEADER *)curve;
  }

  image->group(0, image->getStrokeCount());

  return getDistance(charcode, nextCharCode);
}

//-----------------------------------------------------------------------------

namespace {

TPoint appDrawChar(TRasterGR8P &outImage, HDC hdc, wchar_t charcode) {
  GLYPHMETRICS gm;
  MAT2 mat2;
  mat2.eM11.fract = 0;
  mat2.eM12.fract = 0;
  mat2.eM21.fract = 0;
  mat2.eM22.fract = 0;
  mat2.eM11.value = 1;
  mat2.eM12.value = 0;
  mat2.eM21.value = 0;
  mat2.eM22.value = 1;

  DWORD charMemorySize =
      GetGlyphOutlineW(hdc, charcode, GGO_GRAY8_BITMAP, &gm, 0, 0, &mat2);

  if (charMemorySize == GDI_ERROR) {
    assert(0);
  }

  int lx = gm.gmBlackBoxX;
  int ly = gm.gmBlackBoxY;

  int wrap = ((lx + 3) >> 2) << 2;

  TRasterGR8P appImage = TRasterGR8P(wrap, ly);
  appImage->clear();
  outImage = appImage->extract(0, 0, lx - 1, ly - 1);
  outImage->lock();
  GetGlyphOutlineW(hdc, charcode, GGO_GRAY8_BITMAP, &gm, wrap * ly,
                   outImage->getRawData(), &mat2);
  outImage->unlock();
  TPoint glyphOrig;
  glyphOrig.x = gm.gmptGlyphOrigin.x;
  glyphOrig.y = gm.gmptGlyphOrigin.y;
  return glyphOrig;
}
}

//-----------------------------------------------------------------------------
// valori compresi tra 0 e 64 (si si, proprio 64 e non 63: sono 65 valori)

TPoint TFont::drawChar(TRasterGR8P &outImage, TPoint &glyphOrigin,
                       wchar_t charcode, wchar_t nextCharCode) const {
  TRasterGR8P grayAppImage;

  TPoint glyphOrig = appDrawChar(grayAppImage, m_pimpl->m_hdc, charcode);

  if (glyphOrig.x < 0) {
    glyphOrigin.x = glyphOrig.x;
    glyphOrig.x   = 0;
  } else
    glyphOrigin.x = 0;

  if (glyphOrig.y < 0) {
    glyphOrigin.y = glyphOrig.y;
    glyphOrig.y   = 0;
  } else
    glyphOrigin.y = 0;

  int srcLx = grayAppImage->getLx();
  int srcLy = grayAppImage->getLy();

  int dstLx = srcLx + glyphOrig.x;
  int dstLy = getMaxHeight();

  outImage = TRasterGR8P(dstLx, dstLy);
  outImage->clear();

  int ty = m_pimpl->m_metrics.tmDescent - 1 + glyphOrig.y;
  assert(ty < dstLy);
  assert(ty >= srcLy - 1);
  grayAppImage->lock();
  outImage->lock();

  for (int sy = 0; sy < srcLy; ++sy, --ty) {
    TPixelGR8 *srcPix = grayAppImage->pixels(sy);
    TPixelGR8 *tarPix = outImage->pixels(ty) + glyphOrig.x;
    for (int x = 0; x < srcLx; ++x) {
      assert(srcPix->value < 65);

      switch (srcPix->value) {
      case 0:
        tarPix->value = 0;
        break;
      default:
        tarPix->value = (srcPix->value << 2) - 1;
        break;
      }
      ++srcPix;
      ++tarPix;
    }
  }
  grayAppImage->unlock();
  outImage->unlock();

  return getDistance(charcode, nextCharCode);
}

//-----------------------------------------------------------------------------

TPoint TFont::drawChar(TRasterCM32P &outImage, TPoint &glyphOrigin, int inkId,
                       wchar_t charcode, wchar_t nextCharCode) const {
  TRasterGR8P grayAppImage;
  TPoint glyphOrig = appDrawChar(grayAppImage, m_pimpl->m_hdc, charcode);

  if (glyphOrig.x < 0) {
    glyphOrigin.x = glyphOrig.x;
    glyphOrig.x   = 0;
  } else
    glyphOrigin.x = 0;

  if (glyphOrig.y < 0) {
    glyphOrigin.y = glyphOrig.y;
    glyphOrig.y   = 0;
  } else
    glyphOrigin.y = 0;

  int srcLx = grayAppImage->getLx();
  int srcLy = grayAppImage->getLy();

  int dstLx = srcLx + glyphOrig.x;
  int dstLy = getMaxHeight();

  outImage = TRasterCM32P(dstLx, dstLy);
  outImage->clear();

  assert(TPixelCM32::getMaxTone() == 255);
  // TPixelCM32
  // bgColor(BackgroundStyle,BackgroundStyle,TPixelCM32::getMaxTone());
  TPixelCM32 bgColor;

  int ty = m_pimpl->m_metrics.tmDescent - 1 + glyphOrig.y;
  assert(ty < dstLy);
  assert(ty >= srcLy - 1);
  grayAppImage->lock();
  outImage->lock();

  for (int sy = 0; sy < srcLy; ++sy, --ty) {
    TPixelGR8 *srcPix  = grayAppImage->pixels(sy);
    TPixelCM32 *tarPix = outImage->pixels(ty) + glyphOrig.x;
    for (int x = 0; x < srcLx; ++x) {
      int tone = 256 - (srcPix->value << 2);

      // grayScale  ToonzImage tone   Meaning
      //         0  255               Bg = PurePaint
      //         1  252
      //                 ...
      //        63    4
      //        64    0               Fg = Pure Ink

      if (tone < 0) tone = 0;

      if (tone >= 255)
        *tarPix = bgColor;
      else
        *tarPix = TPixelCM32(inkId, 0, tone);  // BackgroundStyle,tone);

      ++srcPix;
      ++tarPix;
    }
  }
  grayAppImage->unlock();
  outImage->unlock();

  return getDistance(charcode, nextCharCode);
}

//-----------------------------------------------------------------------------

TPoint TFont::getDistance(wchar_t firstChar, wchar_t secondChar) const {
  int advance;
  BOOL result = GetCharWidth32W(m_pimpl->m_hdc, firstChar, firstChar, &advance);
  assert(result);
  if (m_pimpl->m_hasKerning && secondChar) {
    KerningPairs::iterator it =
        m_pimpl->m_kerningPairs.find(make_pair(firstChar, secondChar));
    if (it != m_pimpl->m_kerningPairs.end()) {
      advance += it->second;
    }
  }
  return TPoint(advance, 0);
}

//-----------------------------------------------------------------------------

int TFont::getMaxHeight() const { return m_pimpl->m_metrics.tmHeight; }

//-----------------------------------------------------------------------------

int TFont::getMaxWidth() const { return m_pimpl->m_metrics.tmMaxCharWidth; }
//-----------------------------------------------------------------------------

int TFont::getLineAscender() const { return m_pimpl->m_metrics.tmAscent; }

//-----------------------------------------------------------------------------

int TFont::getLineDescender() const { return -m_pimpl->m_metrics.tmDescent; }

//-----------------------------------------------------------------------------

bool TFont::hasKerning() const { return m_pimpl->m_hasKerning; }

//-----------------------------------------------------------------------------

bool TFont::hasVertical() const { return m_pimpl->m_hasVertical; }

//=============================================================================
//====================      TFontManager  =====================================
//=============================================================================

//---------------------------------------------------------

struct TFontManager::Impl {
  WindowsFontTable m_families;
  bool m_loaded;

  LOGFONTW m_currentLogFont;
  TFont *m_currentFont;

  // this option is set by library user when he wants to write vertically.
  // In this implementation, if m_vertical is true and the font
  // has the @-version, the library use it.
  bool m_vertical;

  Impl() : m_loaded(false), m_currentFont(0), m_vertical(false) {}
};

//---------------------------------------------------------

TFontManager::TFontManager() { m_pimpl = new TFontManager::Impl(); }

//---------------------------------------------------------

TFontManager::~TFontManager() { delete m_pimpl; }

//---------------------------------------------------------

TFontManager *TFontManager::instance() {
  static TFontManager theManager;
  return &theManager;
}

//---------------------------------------------------------

namespace {
BOOL CALLBACK EnumFamCallBack(CONST LOGFONTW *lplf, CONST TEXTMETRICW *,
                              DWORD FontType, LPARAM data) {
  if (FontType & TRUETYPE_FONTTYPE) {
    LOGFONTW newLplf        = *lplf;
    newLplf.lfHeight        = 200;
    newLplf.lfWidth         = 0;
    WindowsFontTable &table = *(WindowsFontTable *)data;
    table[lplf->lfFaceName] = newLplf;
    return TRUE;
  }
  return TRUE;
}
}

//---------------------------------------------------------

void TFontManager::loadFontNames() {
  if (m_pimpl->m_loaded) return;

  HDC hdc = CreateCompatibleDC(NULL);
  if (!hdc) throw TFontLibraryLoadingError();
  EnumFontFamiliesW(hdc, (LPCWSTR)NULL, (FONTENUMPROCW)EnumFamCallBack,
                    (LPARAM) & (m_pimpl->m_families));
  DeleteDC(hdc);
  hdc = 0;
  if (m_pimpl->m_families.empty()) throw TFontLibraryLoadingError();

  m_pimpl->m_loaded = true;
}

//---------------------------------------------------------

void TFontManager::setFamily(const wstring family) {
  wstring userFamilyName =
      ((family.c_str())[0] == L'@') ? wstring(family.c_str() + 1) : family;
  wstring realFamilyName = (m_pimpl->m_vertical && (family.c_str())[0] != L'@')
                               ? L"@" + family
                               : family;

  wstring currentFamilyName = wstring(m_pimpl->m_currentLogFont.lfFaceName);

  if (currentFamilyName == realFamilyName) return;

  LOGFONTW logfont;
  if (m_pimpl->m_vertical) {
    WindowsFontTable::iterator it = m_pimpl->m_families.find(realFamilyName);
    if (it != m_pimpl->m_families.end())
      logfont = it->second;
    else {
      it = m_pimpl->m_families.find(userFamilyName);
      assert(it != m_pimpl->m_families.end());
      if (it != m_pimpl->m_families.end())
        logfont = it->second;
      else
        throw TFontCreationError();
    }
  } else {
    WindowsFontTable::iterator it = m_pimpl->m_families.find(userFamilyName);
    assert(it != m_pimpl->m_families.end());
    if (it != m_pimpl->m_families.end())
      logfont = it->second;
    else
      throw TFontCreationError();
  }

  if (m_pimpl->m_currentFont) {
    logfont.lfHeight = m_pimpl->m_currentLogFont.lfHeight;
    logfont.lfItalic = m_pimpl->m_currentLogFont.lfItalic;
    logfont.lfWeight = m_pimpl->m_currentLogFont.lfWeight;
  } else
    logfont.lfHeight = 200;

  try {
    HDC hdc        = CreateCompatibleDC(NULL);
    TFont *newfont = new TFont(logfont, hdc);
    delete m_pimpl->m_currentFont;
    m_pimpl->m_currentFont    = newfont;
    m_pimpl->m_currentLogFont = logfont;
  } catch (TException &) {
    throw TFontCreationError();
  }
}

//---------------------------------------------------------

void TFontManager::setTypeface(const wstring typeface) {
  LOGFONTW logfont = m_pimpl->m_currentLogFont;
  logfont.lfItalic =
      (typeface == L"Italic" || typeface == L"Bold Italic") ? TRUE : FALSE;
  logfont.lfWeight =
      (typeface == L"Bold" || typeface == L"Bold Italic") ? 700 : 400;

  try {
    HDC hdc        = CreateCompatibleDC(NULL);
    TFont *newfont = new TFont(logfont, hdc);
    delete m_pimpl->m_currentFont;
    m_pimpl->m_currentFont    = newfont;
    m_pimpl->m_currentLogFont = logfont;
  } catch (TException &) {
    throw TFontCreationError();
  }
}

//---------------------------------------------------------

void TFontManager::setSize(int size) {
  LOGFONTW logfont = m_pimpl->m_currentLogFont;
  logfont.lfHeight = size;
  try {
    HDC hdc        = CreateCompatibleDC(NULL);
    TFont *newfont = new TFont(logfont, hdc);
    delete m_pimpl->m_currentFont;
    m_pimpl->m_currentFont    = newfont;
    m_pimpl->m_currentLogFont = logfont;
  } catch (TException &) {
    throw TFontCreationError();
  }
}

//---------------------------------------------------------

wstring TFontManager::getCurrentFamily() const {
  wstring currentFamilyName =
      (m_pimpl->m_currentLogFont.lfFaceName[0] == L'@')
          ? wstring(m_pimpl->m_currentLogFont.lfFaceName + 1)
          : wstring(m_pimpl->m_currentLogFont.lfFaceName);
  return currentFamilyName;
}

//---------------------------------------------------------

wstring TFontManager::getCurrentTypeface() const {
  if (m_pimpl->m_currentLogFont.lfItalic) {
    if (m_pimpl->m_currentLogFont.lfWeight == 700)
      return L"Bold Italic";
    else
      return L"Italic";
  } else {
    if (m_pimpl->m_currentLogFont.lfWeight == 700)
      return L"Bold";
    else
      return L"Regular";
  }
}

//---------------------------------------------------------

TFont *TFontManager::getCurrentFont() {
  if (m_pimpl->m_currentFont) return m_pimpl->m_currentFont;

  if (!m_pimpl->m_currentFont) loadFontNames();

  assert(!m_pimpl->m_families.empty());
  setFamily(m_pimpl->m_families.begin()->first);

  return m_pimpl->m_currentFont;
}

//---------------------------------------------------------

void TFontManager::getAllFamilies(vector<wstring> &families) const {
  families.clear();
  families.reserve(m_pimpl->m_families.size());
  WindowsFontTable::iterator it = m_pimpl->m_families.begin();
  for (; it != m_pimpl->m_families.end(); ++it) {
    if ((it->first)[0] != L'@') families.push_back(it->first);
  }
}

//---------------------------------------------------------

void TFontManager::getAllTypefaces(vector<wstring> &typefaces) const {
  typefaces.resize(4);
  typefaces[0] = L"Regular";
  typefaces[1] = L"Italic";
  typefaces[2] = L"Bold";
  typefaces[3] = L"Bold Italic";
}

//---------------------------------------------------------

void TFontManager::setVertical(bool vertical) {
  if (m_pimpl->m_vertical == vertical) return;
  m_pimpl->m_vertical = vertical;

  wstring currentFamilyName =
      (m_pimpl->m_currentLogFont.lfFaceName[0] == L'@')
          ? wstring(m_pimpl->m_currentLogFont.lfFaceName + 1)
          : wstring(m_pimpl->m_currentLogFont.lfFaceName);
  if (vertical) currentFamilyName = L'@' + currentFamilyName;
  setFamily(currentFamilyName);
}