// character_manager.cpp: implementation of the TFont class. // ////////////////////////////////////////////////////////////////////// #include "tpixelgr.h" #include "tfont.h" #include "tstroke.h" //#include "tcurves.h" #include "traster.h" #include #include #include //#include #include //#include #include "tvectorimage.h" using namespace std; //============================================================================= typedef map WindowsFontTable; typedef map, 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 tempKernPairs(new KERNINGPAIR[pairsCount]); GetKerningPairsW(hdc, pairsCount, tempKernPairs.get()); for (UINT i = 0; i < pairsCount; i++) { pair 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::max)(); double app2 = point.y.value + ((double)point.y.fract) / (std::numeric_limits::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 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 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; DEFAULT: tarPix->value = (srcPix->value << 2) - 1; } ++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; TFontManager::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 &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 &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); }