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;
			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<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);
}