Fontnamen aus der Fontdatei auslesen

fujitsufan

Erfahrenes Mitglied
Hallo,

kann ich die Fonteigenschaften (Schriftfamilie, Voller Name, Schriftschnitt, usw.)
direkt aus der Fontatei auslesen?
Gibts hierfür eine eigene Methode oder ist dies ein größerer Aufwand?

mfG.
fujitsufan
 
Hallo fujitsufan,

Ja, du kannst diese Informationen selbstverständlich aus der .ttf Datei auslesen. Am besten googelst du mal nach der Spezifikation des True Type Dateiformats. Da findest du relativ viele Informationen.

Gruss
Muepe
 
Hallo Muepe32,

sehr viele Informationen.
Bervor ich ins Forum schreibe, bin ich selbstverständlich auf google unterwegs.
Leider habe ich nicht das passende gefunden bzw. habe ich nicht verstanden.
Ein Codebeispiel wäre schön.

Trotzdem Danke!
fujitsufan
 
Hallo fujitsufan,

Die Spezifikation von Microsoft ist eigentlich ziemlich ausführlich:
http://www.microsoft.com/typography/otspec/otff.htm

Hier mal ein kleiner Beispielcode der für einen Teil der Unmengen an möglichen Formaten und Versionen den Namen der Fontfamilie auszulesen versucht (dabei bin ich aber nicht ganz schlau geworden, wann die Strings jetzt in der Datei selbst als Widechar und wann als multibyte gespeichert sind...). Mit dem Aufbau den der Code hat kannst du auch relativ einfach Collections implementieren, du hast dann eine Serie von Offsets für jede einzelne Font und kannst diese dann wie eine eigene Instanz von TTFFont behandeln.

TTFFont.h
C++:
#pragma once

struct TTFTableHeader
{
	UINT tag;
	UINT checkSum;
	UINT offset;
	UINT length;
};

class TTFFont
{
private:
	std::tstring mFamilyName;
	unsigned short mNumTables;
	unsigned int mFilePos;
	std::vector<BYTE>& mData;

	TTFFont(std::vector<BYTE>& fontData);

	void ParseHeader();
	void ParseTables();
	void ParseNameTable(const TTFTableHeader& header);

	template<typename T>
	void Read(T& value)
	{
		if(sizeof(T) + mFilePos >= mData.size())
			throw std::exception("Tried to read past the end of the stream!");

		memcpy(&value, &mData[mFilePos], sizeof(T));
		mFilePos += sizeof(T);
	}

	template<typename T>
	void Read(std::vector<T>& values)
	{
		if(mFilePos + values.size() * sizeof(T) >= mData.size())
			throw std::exception("Tried to read past the end of the stream!");

		memcpy(&values[0], &mData[mFilePos], values.size() * sizeof(T));
		mFilePos += values.size() * sizeof(T);
	}

	template<typename T>
	bool IsPowerOfTwo(const T& value)
	{
		return (value > 0 && (value & (value - 1)) == 0);
	}

	inline double log2(double val)
	{
	  static const double fac = 1.0 / log(2.0);
	  return log(val) * fac;
	}

	inline void swap32(UINT& value)
	{
#ifndef _BIG_ENDIAN
		BYTE* bytes = (BYTE*)&value;
		value = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
#endif
	}

	inline void swap16(USHORT& value)
	{
#ifndef _BIG_ENDIAN
		value = (value >> 8) | (value << 8);
#endif
	}

public:
	static void ParseFile(const std::tstring& fileName, std::vector<TTFFont*>& fonts);

	const std::tstring& GetFamilyName() { return mFamilyName; }
};

TTFFont.cpp
C++:
#include "stdafx.h"
#include "TTFFont.h"

TTFFont::TTFFont(std::vector<BYTE>& fontData) : mData(fontData), mFilePos(0)
{
	ParseHeader();
	ParseTables();
}

void TTFFont::ParseHeader()
{
	UINT fixedVersion = 0;
	Read(fixedVersion);
	swap32(fixedVersion);

	if(fixedVersion != 0x00010000)
		throw std::exception("Version not supported by this wrapper!");

	Read(mNumTables);
	if(mNumTables == 0)
		throw std::exception("Font file does not contain any tables!");

	swap16(mNumTables);

	USHORT searchRange, entrySelector, rangeShift;
	Read(searchRange);
	Read(entrySelector);
	Read(rangeShift);
	swap16(searchRange);
	swap16(entrySelector);
	swap16(rangeShift);

	searchRange /= 16;
	if(!IsPowerOfTwo(searchRange))
		throw std::exception("searchRange in OTF header is not correct (no power of two)!");

	if(searchRange > mNumTables || (searchRange * 2) <= mNumTables)
		throw std::exception("searchRange in OTF header is not correct!");

	if(entrySelector != (USHORT)log2(searchRange))
		throw std::exception("entrySelector in OTF header is not correct!");
}

void TTFFont::ParseTables()
{
	std::vector<TTFTableHeader> tableHeaders(mNumTables);
	Read(tableHeaders);
	bool hasNameTag = false;

	std::for_each(tableHeaders.begin(), tableHeaders.end(),
	[this, &hasNameTag] (TTFTableHeader& table)
	{
		swap32(table.tag);
		swap32(table.checkSum);
		swap32(table.offset);
		swap32(table.length);

		if(table.tag == 'name' && !hasNameTag)
		{
			unsigned int tmpOfs = mFilePos;
			ParseNameTable(table);
			mFilePos = tmpOfs;
			hasNameTag = true;
			return;
		}
	}
	);

	if(hasNameTag == false)
		throw std::exception("Font file has no name table!");
}

void TTFFont::ParseNameTable(const TTFTableHeader& header)
{
	if(header.offset + header.length >= mData.size())
		throw std::exception("Table exceeds file boundaries!");

	struct NameRecord
	{
		USHORT platformID;
		USHORT encodingID;
		USHORT languageID;
		USHORT nameID;
		USHORT length;
		USHORT offset;
	};


	mFilePos = header.offset;
	UINT tableStart = mFilePos;
	USHORT format, count, stringOffset;
	Read(format);
	Read(count);
	Read(stringOffset);
	swap16(format);
	swap16(count);
	swap16(stringOffset);

	if(count == 0)
		throw std::exception("Name table has no name records!");

	std::vector<NameRecord> nameRecords(count);
	Read(nameRecords);
	/* Optional (if format == 1):
	langTagCount
	langTagRecord[langTagCount]
	*/

	bool familyFound = false;
	std::for_each(nameRecords.begin(), nameRecords.end(),
	[this, &stringOffset, &familyFound, tableStart](NameRecord& record)
	{
		swap16(record.platformID);
		swap16(record.encodingID);
		swap16(record.languageID);
		swap16(record.nameID);
		swap16(record.length);
		swap16(record.offset);

		if(record.nameID == 1 && !familyFound)
		{
			if(stringOffset + (UINT)record.offset + record.length >= mData.size())
				throw std::exception("Family name exceeds file boundaries!");

			// 3 => Windows
			if(record.platformID != 3)
				throw std::exception("Only windows platform supported by this implementation!");

			// maybe there are more ways to encode an unicode string, but as its only a test...
			bool isUnicode = record.encodingID == 1;

			std::vector<BYTE> stringData;
			auto start = mData.begin() + stringOffset + tableStart;
			stringData.assign(start + record.offset, start + record.offset + record.length);
			stringData.push_back(0); // just to be sure

			if(isUnicode)
			{
				stringData.push_back(0); // we need one more 0-terminator (USHORT)
				USHORT* strPtr = (USHORT*)&stringData[0];
				std::for_each(strPtr, strPtr + stringData.size() / sizeof(USHORT),
					std::tr1::bind(&TTFFont::swap16, this, std::tr1::placeholders::_1));
				);
			}

#ifdef UNICODE
			if(isUnicode)
				mFamilyName = (TCHAR*)&stringData[0];
			else
			{
				std::vector<wchar_t> wc(stringData.size());
				MultiByteToWideChar(CP_ACP, 0, (char*)&stringData[0], wc.size(),
					&wc[0], wc.size());
				
				mFamilyName = &wc[0];
			}
#else
			if(isUnicode)
			{
				std::vector<char> ansi(stringData.size() / sizeof(USHORT));
				WideCharToMultiByte(CP_ACP, 0, (wchar_t*)&stringData[0],
					ansi.size(), &ansi[0], ansi.size(), NULL, NULL);

				mFamilyName = (TCHAR*)&ansi[0];
			}
			else
				mFamilyName = (TCHAR*)&stringData[0];
#endif
		}
	}
	);
}

void TTFFont::ParseFile(const std::tstring& fileName, std::vector<TTFFont*>& fonts)
{
	std::ifstream inFile(fileName, std::ios::in | std::ios::binary);
	if(!inFile.is_open())
		throw std::exception("The specified file could not be opened!");

	UINT tag = 0;
	if(!inFile.read((char*)&tag, sizeof(tag)))
		throw std::exception("Unable to read tag!");

	if(tag == 'fctt')
		throw std::exception("Collections not yet implemented!");

	inFile.seekg(0, std::ios::end);
	std::streamoff len = inFile.tellg();
	inFile.seekg(0, std::ios::beg);

	std::vector<BYTE> data((UINT)len);
	if(!inFile.read((char*)&data[0], len))
		throw std::exception("Unable to read file content!");

	TTFFont* font = new TTFFont(data);
	fonts.push_back(font);
	return;
}

stdafx.h
C++:
#pragma once

#include <sdkddkver.h>
#include <tchar.h>

#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <algorithm>
#include <functional>
#include <Windows.h>

#ifdef UNICODE
#define tcout std::wcout
#else
#define tcout std::cout
#endif

namespace std
{
	typedef std::basic_string<TCHAR> tstring;
};

Eine mögliche Implementation sähe dann zum Beispiel so aus:
C++:
#include "stdafx.h"
#include "TTFFont.h"


int _tmain(int argc, _TCHAR* argv[])
{
	if(argc < 2)
	{
		tcout << _T("Usage: TTFReader.exe <TTF file1> ...") << std::endl;
		return 0;
	}

	try
	{
		for(int i = 1; i < argc; ++i)
		{
			std::vector<TTFFont*> fonts;
			TTFFont::ParseFile(argv[i], fonts);

			std::for_each(fonts.begin(), fonts.end(), 
			[](TTFFont* font)
			{
				tcout << font->GetFamilyName() << std::endl;
				delete font;
			}
			);
		}
	}
	catch(std::exception& e)
	{
		std::cout << e.what() << std::endl;
	}

	return 0;
}

Das ist ein Anfang und natürlich noch lange nicht vollständig!

Gruss
Muepe

PS:
Im Code wird teilweise C++0x verwendet.
 
Zuletzt bearbeitet:
Hallo Muepe

vielen Dank!

Ist es möglich zu erzwingen, dass auch mehrere Fonts der gleichen Familie in der Privaten Schrift - Tabelle registriert werden? Ob dies Sinn macht oder nicht, sei mal dahin gestellt.

Der Hintergrund ist der, dass bei Verwendung der Fonts die Auswahl "Kursiv, Fett, usw." nicht über eine Auswahlbox getroffen wird, sonder über die Auswahl des betreffenden Fonts erfolgen soll.

// Add font files to the private collection.
privateFontCollection.AddFontFile(L"C:\\WINNT\\Fonts\\Arial.ttf");
privateFontCollection.AddFontFile(L"C:\\WINNT\\Fonts\\Arial Kursiv.ttf");
privateFontCollection.AddFontFile(L"C:\\WINNT\\Fonts\\Arial Fett.ttf");
privateFontCollection.AddFontFile(L"C:\\WINNT\\Fonts\\Cour.ttf");
privateFontCollection.AddFontFile(L"C:\\WINNT\\Fonts\\Times.ttf");

Bei diesen Anweisungen werden nur 3 Schriften in der Privat Schriften - Tabelle registriert.


mfG.
fujitsufan
 
Hallo,

Die kursive Version von Arial heisst ariali.ttf und die fette Variante arialbd.ttf.

Gruss
Muepe
 
Hallo Muepe32,

ja schon.
die abgewandelten Fonts (ariali.ttf und arialbd.ttf) werden bei der Registrierung in der Privat Schriften - Tabelle nicht berücksichtigt, weil sie alle der Schriftenfamilie "Arial" angehören.

Also stehen diese mir nicht zur Verfügung, und da liegt das Problem.

mfG.
fujitsufan
 
Hallo fujitsufan,

Doch, diese stehen dir zur Verfügung. Sie haben nicht einen anderen Namen sondern lediglich einen anderen Fontstyle. Diesen legst du dann im Konstruktor der Klasse Gdiplus::Font fest:
C++:
Font(
  [in]  const FontFamily *family,
  [in]  REAL emSize,
  [in]  INT style, // <-----
  [in]  Unit unit
);

Wobei das dann aus der folgenden Enumeration (Gdiplus::FontStyle) ist:
C++:
typedef enum  {
  FontStyleRegular      = 0,
  FontStyleBold         = 1,
  FontStyleItalic       = 2,
  FontStyleBoldItalic   = 3,
  FontStyleUnderline    = 4,
  FontStyleStrikeout    = 8 
} FontStyle;

Gruss
Muepe
 
Zurück