File:  [Isaki's NoNo m68k/m88k emulator] / nono / util / ttfext / ttfext.cpp
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:27 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, v026, v025, v024, v023, v022, v021, v020, HEAD
nono 1.0.0

//
// nono
// Copyright (C) 2022 nono project
// Licensed under nono-license.txt
//

//
// TTF から埋め込みビットマップを抜き出して BDF にする。
//

// TTF の構造についてはこの辺参照。
// - https://learn.microsoft.com/en-us/typography/opentype/spec/ > Tables
//
// - OpenType ファイル構造
//   https://aznote.jakou.com/prog/opentype/02_struct.html

#include "header.h"
#include "autofd.h"
#include "mystring.h"
#include <errno.h>
#include <fcntl.h>
#include <iconv.h>
#include <unistd.h>
#include <array>
#include <vector>

#define HOWMANY(x, y)	(((x) + (y - 1)) / (y))

static constexpr uint32
FOURCC(const char *str)
{
	return (str[0] << 24) | (str[1] << 16) | (str[2] << 8) | str[3];
}

static int debug = 1;
static const char *new_family_name;

//
// ファイルアクセス
//
class File
{
 public:
	File() {
		fd = -1;
	}
	File(const File& rhs) {
		fd = rhs.fd;
	}
	~File() {
		Close();
	}

	bool Create(const std::string& filename_) {
		filename = filename_;
		fd = open(filename.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0644);
		if (fd < 0) {
			err(1, "open: %s", filename.c_str());
		}
		return true;
	}
	bool Open(const std::string& filename_) {
		filename = filename_;
		fd = open(filename.c_str(), O_RDONLY);
		if (fd < 0) {
			err(1, "open: %s", filename.c_str());
		}
		return true;
	}

	void Close() {
		if (fd >= 0) {
			close(fd);
		}
	}

	ssize_t Read(void *buf, size_t len) {
		auto r = read(fd, buf, len);
		if (r < 0) {
			err(1, "File.Read(%d)", fd);
		}
		return r;
	}

	ssize_t Write(const void *buf, size_t len) {
		auto r = write(fd, buf, len);
		if (r < 0) {
			err(1, "File.Write(%d)", fd);
		}
		return r;
	}

	// TTF はビッグエンディアンなので、専用読み込みルーチンを用意
	uint8 Read1() {
		uint8 buf[1];
		Read(&buf, sizeof(buf));
		return buf[0];
	}

	uint16 Read2() {
		uint8 buf[2];
		Read(&buf, sizeof(buf));
		return (buf[0] << 8) | buf[1];
	}

	uint32 Read4() {
		uint8 buf[4];
		Read(&buf, sizeof(buf));
		return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
	}

	// 便利用
	ssize_t Write(const std::string& str) {
		return Write(str.data(), str.length());
	}

	ssize_t Print(const char *fmt, ...) {
		char buf[1024];
		va_list ap;
		va_start(ap, fmt);
		vsnprintf(buf, sizeof(buf), fmt, ap);
		va_end(ap);
		return Write(buf, strlen(buf));
	}

	off_t Seek(off_t off) {
		auto r = lseek(fd, off, SEEK_SET);
		if (r < 0) {
			err(1, "File.Seek(%d, %jd)", fd, (intmax_t)off);
		}
		return r;
	}

	off_t Tell() const {
		auto r = lseek(fd, 0, SEEK_CUR);
		if (r < 0) {
			err(1, "File.Tell(%d)", fd);
		}
		return r;
	}

	const std::string& GetName() const { return filename; }

 private:
	int fd {};
	std::string filename {};
};

// UTF-16 文字列 srcbuf を UTF-8 文字列に変換して返す。
// from はエンコーディング名で "utf-16be" とか。
// UTF-16 は '\0' を含むので std::string が使えず他のエンコーディングの
// 場合と共通化しづらい。
static std::string
FromUTF16(const char *from, const std::vector<uint8>& srcbuf)
{
	const char *to = "utf-8";
	auto cd = iconv_open(to, from);
	if (cd == (iconv_t)-1) {
		err(1, "iconv_open(%s, %s)", to, from);
	}

	const char *src = (const char *)&srcbuf[0];
	size_t srcleft = srcbuf.size();
	std::vector<char> dstbuf(srcbuf.size() * 3);	// 適当
	char *dst = &dstbuf[0];
	size_t dstleft = dstbuf.size();

	auto r = ICONV(cd, &src, &srcleft, &dst, &dstleft);
	iconv_close(cd);
	if (r == (size_t)-1 && errno != EILSEQ) {
		err(1, "iconv failed");
	}

	dst[dstbuf.size() - dstleft] = '\0';
	return std::string(dstbuf.data());
}


//
// ここから TTF
//

struct NameRecord
{
	uint16 platformID;
	uint16 encodingID;
	uint16 languageID;
	uint16 nameID;
	uint16 length;
	uint16 stringOffset;

	std::string name;

	// nameID の表示用文字列を返す
	std::string GetNameIDStr() const {
		return GetNameIDStr(nameID);
	}
	static std::string GetNameIDStr(uint id);
};

// 各パラメータの意味はここのサイトの中ほどにある g の図参照。
// https://learn.microsoft.com/en-us/typography/opentype/spec/eblc
//
// horiAdvance, vertAdvance が要するにフォントサイズ。
struct BigGlyphMetrics
{
	uint8 height;		// Number of rows of data.
	uint8 width;		// Number of columns of data.
	int8  horiBearingX;	// Distance in pixels from the horizontal origin to
						// the left edge of the bitmap.
	int8  horiBearingY;	// Distance in pixels from the horizontal origin to
						// the top edge of the bitmap.
	uint8 horiAdvance;	// Horizontal advance width in pixels.
	int8  vertBearingX;	// Distance in pixels from the vertical origin to
						// the left edge of the bitmap.
	int8  vertBearingY;	// Distance in pixels from the vertical origin to
						// the top edge of the bitmap.
	uint8 vertAdvance;	// Vertical advance width in pixels.

	// file からこの構造体をロードする
	void Load(File& file);
};

// IndexSubTable にいろいろ混ぜてしまう。
class IndexSubTable
{
 public:
	// indexSubTable
	uint16 firstGlyphIndex;
	uint16 lastGlyphIndex;
	uint32 additionalOffsetToIndexSubTable;	// サブテーブルまでのオフセット

	// indexSubHeader
	uint16 indexFormat;				// Format of this IndexSubTable.
	uint16 imageFormat;				// Format of EBDT image data.
	uint32 imageDataOffset;			// Offset to image data in EBDT table.

	// indexSubTable1
	// first..last までの何かの EBDT 内でのオフセット
	std::vector<uint32> sbitOffset;

	// indexSubTable2
	uint32 imageSize;				// All the glyphs are of the same size.
	BigGlyphMetrics bigMetrics;		// All glyphs have the same metrics;
									// glyph data may be compressed,
									// byte-aligned, or bit-aligned.
};

class SbitLineMetrics
{
 public:
	int8  ascender;
	int8  descender;	// 上を正として、ベースラインからの距離。なので負。
	uint8 widthMax;
	int8  caretSlopeNumerator;
	int8  caretSlopeDenominator;
	int8  caretOffset;
	int8  minOriginSB;
	int8  minAdvanceSB;
	int8  maxBeforeBL;
	int8  minAfterBL;
	int8  pad1;
	int8  pad2;

	// file からこの構造体をロードする
	void Load(File& file);
};

class BitmapSize
{
 public:
	// flags。BigGlyphMetrics とかで使う。
	enum {
		HORIZONTAL_METRICS	= 0x01,
		VERTICAL_METRICS	= 0x02,
	};

 public:
	uint32 indexSubTableArrayOffset;
	uint32 indexTablesSize;
	uint32 numberOfIndexSubTables;
	uint32 colorRef;
	SbitLineMetrics hori;
	SbitLineMetrics vert;
	uint16 startGlyphIndex;
	uint16 endGlyphIndex;
	uint8 ppemX;
	uint8 ppemY;
	uint8 bitDepth;
	uint8 flags;

	std::vector<IndexSubTable> indexSubTable {};
};

class EncodingRecord
{
 public:
	uint16 platformID;
	uint16 encodingID;
	uint32 subtableOffset;

	std::string ToString() const;
};

class CMAPSubTableFormat4
{
 public:
	uint16 format;					// Format number is set to 4.

	uint16 length;					// This is the length in bytes of the
									// subtable.
	uint16 language;
	uint16 segCountX2;				// 2 × segCount.

	uint16 searchRange;				// Maximum power of 2 less than or
									// equal to segCount,
									// times 2 ((2**floor(log2(segCount))) * 2

	uint16 entrySelector;			// Log_2 of the maximum power of 2 less
									// than or equal to numTables
									// (log2(searchRange/2),
									// which is equal to floor(log2(segCount)))

	uint16 rangeShift;				// segCount times 2, minus searchRange
									// ((segCount * 2) - searchRange)

	std::vector<uint16> endCode;	// End characterCode for each segment,
									// last=0xFFFF.

	std::vector<uint16> startCode;	// Start character code for each segment.
	std::vector<int16>  idDelta;	// Delta for all character codes in segment.
	std::vector<uint16> idRangeOffsets;
									// Offsets into glyphIdArray or 0
	std::vector<uint16> glyphIdArray;
									// Glyph index array (arbitrary length)
};

enum NameID
{
	Copyright = 0,
	FontFamilyName,
	FontSubfamilyName,
	UniqueFontIdentifier,
	FullFontName,
	VersionString,
	PostScriptName,
	Trademark,
	ManufacturerName,
	Designer,
	Description,
	URLVendor,
	URLDesigner,
	LicenseDescription,
	LicenseInfoURL,
	reserved,
	TypoGraphicFamilyName,
	TypographicSubfamilyName,
	CompatibleFull,
	SampleText,
	PostScriptCIDFindfontName,
	WWSFamilyName,
	WWSSubfamilyName,
	LightBackgroundPalette,
	DarkBackgroundPalette,
	VariationsPostScriptNamePrefix,
};

class TTF_name
{
	enum {
		Platform_Unicode = 0,
		Platform_Macintosh = 1,
		Platform_Windows = 3,
	};

 public:
	TTF_name(File& file, off_t offset);

	// 指定の NameID の値を取得する
	std::string GetName(NameID id) const;

 private:
	static std::string GetPlatformIDStr(uint id);

	std::vector<NameRecord> nameRecord {};
};

class TTF_EBLC
{
 public:
	TTF_EBLC(const File& file_, off_t offset);

	std::vector<BitmapSize> bitmapSizes {};

	// グリフ数を返す
	int GetGlyphCount() const { return endGlyphIndex - startGlyphIndex + 1; }
	// フォントパラメータを返す
	int GetFontsize() const { return fontsize; }
	int GetAscent() const	{ return ascent; }
	int GetDescent() const	{ return descent; }

 private:
	File file {};

	uint startGlyphIndex {};
	uint endGlyphIndex {};
	uint fontsize {};
	int  ascent {};
	int  descent {};		// 正数とする
};


class TTF_EBDT
{
 public:
	TTF_EBDT(const File& file_, off_t offset);
};

class TTF_cmap
{
 public:
	TTF_cmap(const File& file_, off_t offset);

	void MakeASCIIMap(std::vector<uint>& asciimap);
	void MakeJISMap(std::vector<uint>& jismap);

 private:
	int Code2GID(int code) const;

	File file {};
	std::vector<EncodingRecord> encodingRecords {};
	CMAPSubTableFormat4 sub;
};


//
// TTF を扱うクラス。
// フィールド名などは概ね公式ドキュメントに従う。
//
class TTFFile
{
	static const uint32 NAME_name = FOURCC("name");
	static const uint32 NAME_EBDT = FOURCC("EBDT");
	static const uint32 NAME_EBLC = FOURCC("EBLC");
	static const uint32 NAME_cmap = FOURCC("cmap");

 public:
	TTFFile();
	~TTFFile();

	bool Open(const File& file_);
	void SaveBDFAscii(File& outfile);
	void SaveBDFKanji(File& outfile);

	// ASCII から GID への変換テーブル
	std::vector<uint> asciimap {};
	// JIS から GID への変換テーブル
	std::vector<uint> jismap {};

	// グリフデータ。GID => data[]
	std::vector<std::vector<uint8>> glyph {};

 private:
	void LoadGlyph();
	void LoadGlyphFormat1_7(const IndexSubTable& sub);
	void LoadGlyphFormat2_5(const IndexSubTable& sub);
	std::string DebugBitmapData(int, const std::vector<uint8>& data);
	void WriteBDFHeader(File& outfile, int width);
	std::string MakeChar(uint code, int, int, const std::vector<uint8>& data);

	File file {};
	std::unique_ptr<TTF_name> name {};
	std::unique_ptr<TTF_EBLC> eblc {};
	std::unique_ptr<TTF_cmap> cmap {};
	int fontsize {};
	int ascent {};
	int descent {};		// 正数とする

	uint32 offset_name {};
	uint32 offset_EBDT {};
	uint32 offset_EBLC {};
	uint32 offset_cmap {};
};

// コンストラクタ
TTFFile::TTFFile()
{
	asciimap.resize(256);
	// JIS は上位下位とも (0x21 .. 0x7e) の範囲
	jismap.resize(0x5e * 0x5e);
}

// デストラクタ
TTFFile::~TTFFile()
{
}

// ファイルから情報を取得
bool
TTFFile::Open(const File& file_)
{
	file = file_;

	// 先頭のファイルヘッダ(オフセットテーブルというらしい)は 12バイト
	uint32 version = file.Read4();
	uint32 numTables = file.Read2();
	uint32 searchRange __unused = file.Read2();
	uint32 entrySelector __unused = file.Read2();
	uint32 rangeShift __unused = file.Read2();
	if (debug) {
		printf("version = %08x\n", version);
		printf("numTables = %d\n", numTables);
	}

	// テーブルを調べて、必要なセクションの開始位置を取得
	for (int i = 0; i < numTables; i++) {
		uint32_t namecc = file.Read4();
		uint32_t csum   = file.Read4();
		uint32_t offset = file.Read4();
		uint32_t len = file.Read4();

		(void)csum;

		if (debug) {
			printf("[%d] %c%c%c%c off=%08x len=%08x\n",
				i,
				(namecc >> 24),
				(namecc >> 16) & 0xff,
				(namecc >> 8)  & 0xff,
				(namecc)       & 0xff,
				offset,
				len);
		}

		if (namecc == NAME_name) {
			offset_name = offset;
		}
		if (namecc == NAME_EBDT) {
			offset_EBDT = offset;
		}
		if (namecc == NAME_EBLC) {
			offset_EBLC = offset;
		}
		if (namecc == NAME_cmap) {
			offset_cmap = offset;
		}
	}

	// 必要なセクションが全部揃ってなければエラー終了
	if (offset_name == 0) {
		errx(1, "%s: no name section", file.GetName().c_str());
	}
	if (offset_EBDT == 0) {
		errx(1, "%s: no EBDT section", file.GetName().c_str());
	}
	if (offset_EBLC == 0) {
		errx(1, "%s: no EBLC section", file.GetName().c_str());
	}
	if (offset_cmap == 0) {
		errx(1, "%s: no cmap section", file.GetName().c_str());
	}

	// name セクションの読み込み。
	// name セクションは Copyright などのプロパティなど。
	name.reset(new TTF_name(file, offset_name));

	// EBLC セクションの読み込み。
	// EBLC セクションはビットマップグリフに関する情報(サイズや格納方法)。
	eblc.reset(new TTF_EBLC(file, offset_EBLC));
	fontsize = eblc->GetFontsize();
	ascent   = eblc->GetAscent();
	descent  = eblc->GetDescent();
	// それによってグリフ領域を確保。
	glyph.resize(eblc->GetGlyphCount());

	// cmap セクションの読み込み。
	// cmap セクションは文字コードと GID(内部のGlyph ID) とのマッピングなど。
	cmap.reset(new TTF_cmap(file, offset_cmap));

	// グリフインデックスを求める。
	cmap->MakeASCIIMap(asciimap);
	cmap->MakeJISMap(jismap);

	// EBDT セクションからフォントデータを読み込み。
	// EBDT セクションはほぼビットマップデータ置き場。
	LoadGlyph();

	return true;
}

// eblc で列挙されているすべてのグリフを読み込む。
void
TTFFile::LoadGlyph()
{
	// bitmapSizez[]->indexSubTable[] の2段階のテーブル。
	for (const auto& bs : eblc->bitmapSizes) {
		for (const auto& sub : bs.indexSubTable) {
			// indexFormat はグリフのパラメータを持っているこのインデックス
			// 自体が (共用体のようになっていて) どの形式かを示している。
			// imageFormat は EBDT 内のグリフデータがどの形式かを示している。
			// indexFormat と imageFormat は対象のグリフの性質によって
			// ある程度組み合わせが決まっている。
			if (sub.indexFormat == 1 && sub.imageFormat == 7) {
				LoadGlyphFormat1_7(sub);
			} else if (sub.indexFormat == 2 && sub.imageFormat == 5) {
				LoadGlyphFormat2_5(sub);
			} else {
				// 他の組み合わせは出てこないので無視
				errx(1, "Unsupported index=%d image=%d format",
					sub.indexFormat, sub.imageFormat);
			}
		}
	}
}

// indexFormat=1, imageFormat=7
void
TTFFile::LoadGlyphFormat1_7(const IndexSubTable& sub)
{
	if (sub.lastGlyphIndex - sub.firstGlyphIndex + 1 != sub.sbitOffset.size()) {
		errx(1, "sbitOffset mismatch?");
	}

	// indexFormat==1 は
	// GlyphIndex の数分 sbitOffset[] で EBDT 内オフセットが指定してある。
	//
	// 例えば
	//  firstGlyphIndex = 0
	//  lastGlyphIndex  = 2
	//  imageDataOffset = 00000004
	//  sbitOffset[]    = { 00000000, 0000000c, 00000015 }
	// の場合、グリフデータは EBDT 先頭から数えて
	// GID=0 は $00000004+$00000000 = $00000004 から
	// GID=1 は $00000004+$0000000c = $00000010 から
	// GID=2 は $00000004+$00000015 = $00000019 から
	// 始まる。
	//
	// imageFormat==7 は "big metrics, bit-aligned data" 形式で、EBDT 内の
	// 指定オフセットの位置には
	//  BigGlyphMetrics bigMetrics;
	//  uint8           imageData[];
	// が記録されている。
	//
	// 例えば三点リーダのような疎なグリフに有効で、
	// BigGlyphMetrics でオフセットが指定されており、
	// imageData[] は実際の描画点のある下図中枠の部分だけでよい、という構造。
	//
	//   BigGlyphMetrics.horiBearingX
	//   |->|
	//   +-------------+
	//   |             |
	//   |  +-------+  |---
	//   |  |X  X  X|  | ^
	//   |  +-------+  | | BigGlyphMetrics.horiBearingY
	//   |             | |
	//   +-------------+--- Baseline
	//   |             |
	//   +-------------+

	for (int i = 0, end = sub.sbitOffset.size(); i < end; i++) {
		uint32 offset = sub.sbitOffset[i];
		uint gid = sub.firstGlyphIndex + i;

		file.Seek(offset_EBDT + sub.imageDataOffset + offset);
		BigGlyphMetrics big;
		big.Load(file);
		if (debug) {
			printf(" %08x %dx%d horiXY=(%d,%d) vertXY=(%d,%d) Adv=%dx%d\n",
				offset,
				big.width, big.height,
				big.horiBearingX,
				big.horiBearingY,
				big.vertBearingX,
				big.vertBearingY,
				big.horiAdvance,
				big.vertAdvance);
		}

		// 64x64 ドットの領域を用意して一旦左上詰めで imageData[] を読み込む。
		std::vector<uint64> map;
		int bytelen = HOWMANY(big.width * big.height, 8);
		int width = big.width;
		uint64 input = 0;
		int nbit = 0;
		for (int j = 0; j < bytelen; j++) {
			// 入力キューに下から詰めていく
			input = (input << 8) | file.Read1();
			nbit += 8;
			// 1ライン分を超えてる間切り出す
			while (nbit >= width) {
				uint64 bytedata;
				// 1ライン分を右詰めして..
				bytedata = input >> (nbit - width);
				// MSB 詰めし直して..
				bytedata <<= (64 - width);
				// そのまま map の MSB 詰め
				map.push_back(bytedata);
				// 取り出した分を除外
				input <<= 64 - (nbit - width);
				input >>= 64 - (nbit - width);
				nbit -= width;
			}
		}

		// X方向へオフセット。
		// HORIZONTAL_METRICS では horiBearingX は左に入れるパディング。
		for (auto& m : map) {
			m >>= big.horiBearingX;
		}
		// Y方向へオフセット。
		// HORIZONTAL_METRICS では horiBearingY は基準線からグリフ片の
		// 上端までの上方向へのオフセット。基準線は ascent, descent のやつ。
		int paddingY = big.vertAdvance - 2 - big.horiBearingY;
		for (int j = 0; j < paddingY; j++) {
			map.insert(map.begin(), 0);
		}
		// 縦が足りない分はここで補充
		map.resize(big.vertAdvance);

		// horiAdvance * vertAdvance ビット分を書き出す。
		std::vector<uint8> data;
		for (int y = 0; y < big.vertAdvance; y++) {
			for (int x = 0; x < big.horiAdvance; x += 8) {
				uint8 c = map[y] >> (56 - x);
				data.push_back(c);
			}
		}

		if (0 && debug) {
			printf("gid=%d\n", gid);
			printf("%s\n", DebugBitmapData(big.horiAdvance, data).c_str());
		}

		glyph[gid] = data;
	}
}

// indexFormat=2, imageFormat=5
void
TTFFile::LoadGlyphFormat2_5(const IndexSubTable& sub)
{
	// indexFormat==2 "All glyphs have identical metrics" は
	//  uint32 imageSize;
	//  BigGlyphMetrics bigMetrics;
	// を持っており、ここでメトリックが指定されている。
	// EBDT 内のオフセットは imageDataOffset。
	//
	// imageFormat==5 "metrics in EBLC, bit-aligned image data only" は、
	// データをビット境界で詰め込んであるのでここでバイト境界にする
	// (横 12bit を横 2バイトにする)。

	int nglyph = sub.lastGlyphIndex - sub.firstGlyphIndex + 1;
	uint32 bytes  = sub.imageSize;
	file.Seek(offset_EBDT + sub.imageDataOffset);
	for (int i = 0; i < nglyph; i++) {
		uint32 gid = sub.firstGlyphIndex + i;

		std::vector<uint8> data;
		int width = sub.bigMetrics.width;	// ショートカット
		uint64 input = 0;					// とりあえず横64まで
		int nbit = 0;						// input 内の有効ビット
		for (int j = 0; j < bytes; j++) {
			// 入力キューに下から詰めていく
			input = (input << 8) | file.Read1();
			nbit += 8;
			// 1ライン分を超えたら切り出す
			if (nbit >= width) {
				uint32 bytedata;
				// 1ライン分を右詰めして..
				bytedata = input >> (nbit - width);
				// MSB 詰めし直して..
				bytedata <<= (32 - width);
				// 上から1バイトずつ取り出す
				for (int k = 0; k < width; k += 8) {
					data.push_back(bytedata >> 24);
					bytedata <<= 8;
				}
				// 取り出した部分を除外
				input <<= 64 - (nbit - width);
				input >>= 64 - (nbit - width);
				nbit -= width;
			}
		}
		glyph[gid] = data;

		if (0 && debug) {
			printf("gid=%d\n", gid);
			printf("%s\n", DebugBitmapData(width, data).c_str());
		}
	}
}

// デバッグ用のビットマップデータの表示文字列を返す。
// widthbit はフォントの横幅(ビット)。
// data は byte-aligned のデータ。
std::string
TTFFile::DebugBitmapData(int widthbit, const std::vector<uint8>& data)
{
	std::string str;

	int wbyte = HOWMANY(widthbit, 8);
	std::vector<char> buf(widthbit + 2);

	for (int i = 0, end = data.size(); i < end; i += wbyte) {
		uint32 v = 0;
		for (int j = 0; j < wbyte; j++) {
			v = (v << 8) | data[i + j];
		}
		v <<= (32 - wbyte * 8);
		int b;
		for (b = 0; b < wbyte * 8; b++, v <<= 1) {
			buf[b] = ((int32)v < 0) ? '@' : '.';
		}
		buf[b++] = '\n';
		buf[b] = '\0';

		str += std::string(buf.data());
	}
	return str;
}

// ASCII 部分 (というか半角部分) を BDF で保存する。
void
TTFFile::SaveBDFAscii(File& outfile)
{
	// ヘッダ出力
	WriteBDFHeader(outfile, 1);

	// ここからグリフ出力。
	// 先に要素数を出力する必要があるので一旦集める。
	std::vector<std::string> glypharray;
	for (int code = 0, end = asciimap.size(); code < end; code++) {
		int gid = asciimap[code];
		if (gid != 0) {
			auto line = MakeChar(code, fontsize / 2, fontsize, glyph[gid]);
			glypharray.push_back(line);
		}
	}

	// 出力
	outfile.Print("CHARS %d\n", (int)glypharray.size());
	outfile.Write("\n");
	for (const auto& line : glypharray) {
		outfile.Write(line.c_str());
	}
	outfile.Write("ENDFONT\n");
}

// 全角部分を BDF で保存する。
void
TTFFile::SaveBDFKanji(File& outfile)
{
	// ヘッダ出力
	WriteBDFHeader(outfile, 2);

	// ここからグリフ出力。
	// 先に要素数を出力する必要があるので一旦集める。
	std::vector<std::string> glypharray;
	for (int h = 0; h < 0x5e; h++) {
		for (int l = 0; l < 0x5e; l++) {
			int idx = h * 0x5e + l;
			int gid = jismap[idx];
			int jh = 0x21 + h;
			int jl = 0x21 + l;
			int jiscode = (jh << 8) + jl;
			if (gid != 0) {
				auto line = MakeChar(jiscode, fontsize, fontsize, glyph[gid]);
				glypharray.push_back(line);
			}
		}
	}

	// 出力
	outfile.Print("CHARS %d\n", (int)glypharray.size());
	outfile.Write("\n");
	for (const auto& line : glypharray) {
		outfile.Write(line.c_str());
	}
	outfile.Write("ENDFONT\n");
}

// width は 1 なら半角、2 なら全角。
void
TTFFile::WriteBDFHeader(File& outfile, int width)
{
	// よく分からん
	std::string foundry = name->GetName(NameID::ManufacturerName);
	std::string family_name = name->GetName(NameID::PostScriptName);
	std::string weight = name->GetName(NameID::FontSubfamilyName);
	const char *slant = "R";
	const char *setwidth = "Normal";
	const char *addstyle = "";
	int pixelsize = fontsize;
	int pointsize = 150;
	int resolutionX = 75;
	int resolutionY = 75;
	char spacing = 'C';
	int average_width = fontsize * 10;	// ?
	const char *charset_registry;
	if (width == 1) {
		charset_registry = "ASCII";
	} else {
		charset_registry = "JISX0208.1983";
	}
	const char *charset_encoding = "0";

	if (foundry.empty()) {
		foundry = "misc";
	}

	// SIL OFL は改変フォントに元フォントと同じ名前を使ってはいけない。
	// TTF -> BDF の形式変更も改変になるようにも読めるので、別の名前を使う。
	if (new_family_name) {
		family_name = new_family_name;
	}

	outfile.Write("STARTFONT 2.1\n");
	outfile.Print("FONT -%s-%s-%s-%s-%s-%s-%d-%d-%d-%d-%c-%d-%s-%s\n",
		foundry.c_str(),
		family_name.c_str(),
		weight.c_str(),
		slant,
		setwidth,
		addstyle,
		pixelsize,
		pointsize,
		resolutionX,
		resolutionY,
		spacing,
		average_width,
		charset_registry,
		charset_encoding);
	outfile.Print("SIZE %d %d %d\n", fontsize, resolutionX, resolutionY);
	outfile.Print("FONTBOUNDINGBOX %d %d 0 %d\n",
		fontsize / 2 * width, fontsize, -descent);
	outfile.Write("\n");

	// プロパティ。先頭行に要素数を出力する必要があるので一旦集める
	std::vector<std::string> prop;
#define PROP(str...)	prop.push_back(string_format(str));

	PROP("FONTNAME_REGISTRY \"\"");
	PROP("FOUNDRY \"%s\"", foundry.c_str());
	PROP("FAMILY_NAME \"%s\"", family_name.c_str());
	PROP("WEIGHT_NAME \"%s\"", weight.c_str());
	PROP("SLANT \"%s\"", slant);
	PROP("SETWIDTH_NAME \"%s\"", setwidth);
	PROP("ADD_STYLE_NAME \"%s\"", addstyle);
	PROP("PIXEL_SIZE %d", fontsize);
	PROP("POINT_SIZE %d", pointsize);
	PROP("RESOLUTION_X %d", resolutionX);
	PROP("RESOLUTION_Y %d", resolutionY);
	PROP("SPACING \"%c\"", spacing);
	PROP("AVERAGE_WIDTH %d", average_width);
	PROP("CHARSET_REGISTRY \"%s\"", charset_registry);
	PROP("CHARSET_ENCODING \"%s\"", charset_encoding);
	PROP("COPYRIGHT \"%s License: %s %s\"",
		name->GetName(NameID::Copyright).c_str(),
		name->GetName(NameID::LicenseDescription).c_str(),
		name->GetName(NameID::LicenseInfoURL).c_str());
	PROP("WEIGHT 10");
	PROP("DEFAULT_CHAR 0");	// ?
	PROP("FONT_ASCENT %d", ascent);
	PROP("FONT_DESCENT %d", descent);

	// 勝手に追加。
	std::string v;
	v = name->GetName(NameID::FullFontName);
	if (v.empty() == false) {
		PROP("_COMMENT_TTF_FULL_FONT_NAME \"%s\"", v.c_str());
	}
	v = name->GetName(NameID::VersionString);
	if (v.empty() == false) {
		PROP("_COMMENT_TTF_VERSION \"%s\"", v.c_str());
	}
	v = name->GetName(NameID::Designer);
	if (v.empty() == false) {
		PROP("_COMMENT_TTF_DESIGNER \"%s\"", v.c_str());
	}
	v = name->GetName(NameID::URLVendor);
	if (v.empty() == false) {
		PROP("_COMMENT_TTF_URL_VENDOR \"%s\"", v.c_str());
	}
	v = name->GetName(NameID::URLDesigner);
	if (v.empty() == false) {
		PROP("_COMMENT_TTF_URL_DESIGNER \"%s\"", v.c_str());
	}

	outfile.Print("STARTPROPERTIES %d\n", (int)prop.size());
	for (const auto& s : prop) {
		outfile.Write(s + "\n");
	}
	outfile.Write("ENDPROPERTIES\n");
	outfile.Write("\n");
}

// 1文字分の STARTCHAR .. ENDCHAR までを文字列にしたものを返す。
std::string
TTFFile::MakeChar(uint code, int w, int h, const std::vector<uint8>& data)
{
	std::string lines;

	// STARTCHAR のほうはほぼコメント。ENCODING のほうが文字のコード。
	if (code < 0x100) {
		lines += string_format("STARTCHAR %02x\n", code);
	} else {
		lines += string_format("STARTCHAR %04x\n", code);
	}
	lines += string_format("ENCODING %d\n", code);
	lines += "SWIDTH 960 0\n";
	lines += string_format("DWIDTH %d 0\n", w);
	lines += string_format("BBX %d %d 0 %d\n", w, h, -descent);
	lines += "BITMAP\n";
	// 読み込んだグリフデータは左端を必ずバイト境界から始めてある。
	int widthbytes = data.size() / h;
	for (int y = 0, yend = data.size(); y < yend; y += widthbytes) {
		for (int x = 0; x < widthbytes; x++) {
			lines += string_format("%02x", data[y + x]);
		}
		lines += "\n";
	}
	lines += "ENDCHAR\n";
	lines += "\n";

	return lines;
}


//
// name セクション
//

// コンストラクタ
TTF_name::TTF_name(File& file, off_t offset_name)
{
	file.Seek(offset_name);
	uint32 version = file.Read2();
	uint32 count = file.Read2();
	uint32 storageOffset = file.Read2();

	nameRecord.resize(count);
	for (int i = 0; i < count; i++) {
		auto& rec = nameRecord[i];
		rec.platformID = file.Read2();
		rec.encodingID = file.Read2();
		rec.languageID = file.Read2();
		rec.nameID     = file.Read2();
		rec.length    = file.Read2();
		rec.stringOffset = file.Read2();
	}
	for (auto& rec : nameRecord) {
		file.Seek(offset_name + storageOffset + rec.stringOffset);
		if (rec.platformID == Platform_Windows) {
			// UTF-16BE エンコーディングと規定されている
			std::vector<uint8> src(rec.length);
			for (int j = 0; j < rec.length; j++) {
				src[j] = file.Read1();
			}
			rec.name = FromUTF16("utf-16be", src);
		} else {
			// とりあえず ASCII だと思ってそのまま使う
			for (int j = 0; j < rec.length; j++) {
				rec.name += (char)file.Read1();
			}
		}
	}

	if (debug) {
		for (int i = 0, end = nameRecord.size(); i < end; i++) {
			const auto& rec = nameRecord[i];
			printf("nameRecord[%d].platformID = %d(%s)\n",
				i, rec.platformID, GetPlatformIDStr(rec.platformID).c_str());
			printf("nameRecord[%d].encodingID = %d\n", i, rec.encodingID);
			printf("nameRecord[%d].languageID = %d\n", i, rec.languageID);
			printf("nameRecord[%d].nameID = %d(%s)\n", i, rec.nameID,
				rec.GetNameIDStr().c_str());
			printf("nameRecord[%d].name = \"%s\"\n", i, rec.name.c_str());
		}
	}
	(void)version;
	(void)storageOffset;
}

// 指定の文字列を返す。
// とりあえず ASCII だけっぽい Macintosh のほうから返す。どうしたもんか。
std::string
TTF_name::GetName(NameID id) const
{
	for (const auto& rec : nameRecord) {
		if (rec.platformID == Platform_Macintosh && rec.nameID == id) {
			return rec.name;
		}
	}
	return "";
}

// デバッグ用に platformID を文字列にして返す
/*static*/ std::string
TTF_name::GetPlatformIDStr(uint id)
{
	static const char * const platform_names[] = {
		"Unicode",
		"Macintosh",
		"",
		"Windows",
	};

	if (id < countof(platform_names)) {
		return platform_names[id];
	} else {
		return "";
	}
}


//
// EBLC セクション
//

// コンストラクタ
TTF_EBLC::TTF_EBLC(const File& file_, off_t offset_EBLC)
	: file(file_)
{
	startGlyphIndex = 0;
	endGlyphIndex = 65536;

	// EBLC ヘッダ
	//  uint16 majorVersion;
	//  uint16 minorVersion;
	//  uint32 numSizes;
	//  BitmapSize bitmapSizes[];
	// numSizes フィールドの次から、BitmapSize が numSizes 個並んでるっぽい。

	file.Seek(offset_EBLC);
	auto majorVersion = file.Read2();
	auto minorVersion = file.Read2();
	if (majorVersion != 2 || minorVersion != 0) {
		errx(1, "Unknown EBLC version %d.%d", majorVersion, minorVersion); 
	}
	uint32 numSizes = file.Read4();
	if (debug) {
		printf("EBLC.numSizes=%d\n", numSizes);
	}

	// bitmapSizes[] を全部読み込む
	bitmapSizes.resize(numSizes);
	for (int i = 0; i < numSizes; i++) {
		auto& bs = bitmapSizes[i];
		bs.indexSubTableArrayOffset = file.Read4();
		bs.indexTablesSize = file.Read4();
		bs.numberOfIndexSubTables = file.Read4();
		bs.colorRef = file.Read4();
		bs.hori.Load(file);
		bs.vert.Load(file);
		bs.startGlyphIndex = file.Read2();
		bs.endGlyphIndex = file.Read2();
		bs.ppemX = file.Read1();
		bs.ppemY = file.Read1();
		bs.bitDepth = file.Read1();
		bs.flags = file.Read1();
		if (debug) {
			printf("bitmapSizes[%d]\n", i);
#define A(fmt, name)	printf(" %-24s = " #fmt "\n", #name, bs.name)
			A($%08x, indexSubTableArrayOffset);
			A($%08x, indexTablesSize);
			A(%d, numberOfIndexSubTables);
			A(%d, colorRef);
			A(%d, startGlyphIndex);
			A(%d, endGlyphIndex);
			A(%d, ppemX);
			A(%d, ppemY);
			A(%d, bitDepth);
			A($%x, flags);

			if (bs.flags == BitmapSize::HORIZONTAL_METRICS) {
				A(%d, hori.ascender);
				A(%d, hori.descender);
			}
		}
		if (bs.flags != BitmapSize::HORIZONTAL_METRICS) {
			errx(1, "Unsupported bitmap flags $%x", bs.flags);
		}

		// bitmapSizes[] 全体に渡る start..end
		startGlyphIndex = std::max(startGlyphIndex, (uint)bs.startGlyphIndex);
		endGlyphIndex   = std::min(endGlyphIndex,   (uint)bs.endGlyphIndex);

		// 代表値みたいなのは本当はないかもだが、必要なのでとりあえず。
		if (fontsize == 0) {
			fontsize = bs.ppemY;
		} else if (fontsize != bs.ppemY) {
			errx(1, "bs.ppemY=%d but already has %d", bs.ppemY, fontsize);
		}
		if (ascent == 0) {
			ascent = bs.hori.ascender;
		} else if (ascent != bs.hori.ascender) {
			errx(1, "bs.hori.ascender=%d but already has %d",
				bs.hori.ascender, ascent);
		}
		// (TTF の) descender は負数、(BDF の) descent は正数ということにする。
		if (descent == 0) {
			descent = -bs.hori.descender;
		} else if (descent != bs.hori.descender) {
			errx(1, "bs.hori.descender=%d but already has (-)%d",
				bs.hori.descender, descent);
		}
	}

	// 各 bitmapSizes には indexSubTables[] 配列がある。
	for (auto& bs : bitmapSizes) {
		bs.indexSubTable.resize(bs.numberOfIndexSubTables);

		// indexSubTableArrayOffset は EBLC 先頭からのオフセット。
		// indexSubTable[] 自体にはグリフ範囲と additional へのオフセットのみ
		// 記録されている。additional のほうはフォーマットによってサイズが
		// 異なるためだと思う。
		// additional が指す先は indexSubHeader で、ここの indexFormat で
		// この後に続く indexSubTable の種別が決まっている。
		//
		// indexSubTable {
		//   { firstGlyphIndex, lastGlyphIndex }
		//   additionalOffsetToIndexSubTable ----+
		// }                                     |
		//                                       |
		// +-------------------------------------+
		// |
		// +-> indexSubHeader {
		//   indexFormat = 1;
		//   imageFormat;
		//   imageDataOffset --> EBDT 内のオフセット
		// }
		// indexSubTable1 {
		//   IndexSubType;
		//   sbitOffset[];
		// }
		// 
		file.Seek(offset_EBLC + bs.indexSubTableArrayOffset);
		for (int j = 0; j < bs.numberOfIndexSubTables; j++) {
			auto& sub = bs.indexSubTable[j];
			sub.firstGlyphIndex = file.Read2();
			sub.lastGlyphIndex  = file.Read2();
			sub.additionalOffsetToIndexSubTable = file.Read4();
			if (debug) {
				printf("indexSubTable[%d] GlyphIndex=%4d-%4d "
					"additionalOffsetToIndexSubTable=$%08x\n", j,
					sub.firstGlyphIndex,
					sub.lastGlyphIndex,
					sub.additionalOffsetToIndexSubTable);
			}
		}

		for (int j = 0; j < bs.numberOfIndexSubTables; j++) {
			auto& sub = bs.indexSubTable[j];
			file.Seek(offset_EBLC
				+ bs.indexSubTableArrayOffset
				+ sub.additionalOffsetToIndexSubTable);
			// ここが indexSubType header
			sub.indexFormat = file.Read2();
			sub.imageFormat = file.Read2();
			sub.imageDataOffset = file.Read4();
			if (debug) {
				printf("[%d] indexFormat=%d imageFormat=%d "
					"imageDataOffset=$%08x\n",
					j, sub.indexFormat, sub.imageFormat, sub.imageDataOffset);
			}

			switch (sub.indexFormat) {
			 case 1:	// indexSubTable1
			 {
				// IndexSubType header;
				// Offset32     sbitOffset[];
				//
				// 公式ドキュメントの改行位置が微妙に分からんけど。
				//  sbitOffsets[glyphIndex] + imageDataOffset = glyphData;
				//  sizeOfArray = (lastGlyph - firstGlyph + 1) + 1
				//                + 1 pad if needed;
				int nglyph = sub.lastGlyphIndex - sub.firstGlyphIndex + 1;
				sub.sbitOffset.resize(nglyph);
				for (int i = 0; i < nglyph; i++) {
					uint32 offset = file.Read4();
					sub.sbitOffset[i] = offset;
					if (debug) {
						printf(" %08x", offset);
						if (i % 8 == 7) {
							printf("\n");
						}
					}
				}
				if (debug) {
					printf("\n");
				}
				break;
			 }

			 case 2:	// indexSubTable2
			 {
				// IndexSubType    header;
				// uint32          imageSize;
				// BigGlyphMetrics bigMetrics;
				sub.imageSize = file.Read4();
				auto& big = sub.bigMetrics;
				big.Load(file);
				if (debug) {
					printf(" imageSize=%d bigMetrics=%dx%d\n",
						sub.imageSize, big.width, big.height);
				}
				break;
			 }

			 default:
				errx(1, "unsupported indexSubTable%d", sub.indexFormat);
			}
		}
	}
}


//
// cmap セクション
//

// コンストラクタ
TTF_cmap::TTF_cmap(const File& file_, off_t offset)
	: file(file_)
{
	// cmap ヘッダ
	//
	//  uint16 version;		// Table version number (0).
	//  uint16 numTables;	// Number of encoding tables that follow.
	//  EncodingRecord encodingRecords[numTables];

	file.Seek(offset);
	uint16 version = file.Read2();
	uint16 numTables = file.Read2();

	if (version != 0) {
		errx(1, "Unsupported cmap version %d\n", version);
	}

	printf("cmap:\n");
	encodingRecords.resize(numTables);
	for (int i = 0; i < encodingRecords.size(); i++) {
		auto& enc = encodingRecords[i];
		enc.platformID = file.Read2();
		enc.encodingID = file.Read2();
		enc.subtableOffset = file.Read4();

		if (debug) {
			printf("[%d] %s subtableOffset=%08x\n", i,
				enc.ToString().c_str(),
				enc.subtableOffset);
		}
	}

	// とりあえず Unicode2.0 BMP のみターゲットにする。
	EncodingRecord *uenc = NULL;
	for (auto& enc : encodingRecords) {
		if (enc.platformID == 0 && enc.encodingID == 3) {
			uenc = &enc;
		}
	}
	if (uenc == NULL) {
		errx(1, "cmap: Unicode2.0 BMP mapping not found");
	}

	// Unicode2.0 BMP の場合 offset の先は Format4 らしい。
	// subtableOffset は cmap 先頭からのオフセット。
	off_t tablestart = file.Seek(offset + uenc->subtableOffset);
	sub.format = file.Read2();
	sub.length = file.Read2();
	sub.language = file.Read2();
	sub.segCountX2 = file.Read2();
	sub.searchRange = file.Read2();
	sub.entrySelector = file.Read2();
	sub.rangeShift = file.Read2();

	int segCount = sub.segCountX2 / 2;
	sub.endCode.resize(segCount);
	sub.startCode.resize(segCount);
	sub.idDelta.resize(segCount);
	sub.idRangeOffsets.resize(segCount);

	for (int i = 0; i < segCount; i++) {
		sub.endCode[i] = file.Read2();
	}
	file.Read2();	// reservedPad
	for (int i = 0; i < segCount; i++) {
		sub.startCode[i] = file.Read2();
	}
	for (int i = 0; i < segCount; i++) {
		sub.idDelta[i] = file.Read2();
	}
	for (int i = 0; i < segCount; i++) {
		sub.idRangeOffsets[i] = file.Read2();
	}
	// glyphIdArray[] の長さは直接は書いてないが、sub.length が
	// (たぶん subtable 先頭からの) subtable のバイト長らしいので、そこまで。
	int remain = sub.length - (file.Tell() - tablestart);
	sub.glyphIdArray.resize(remain / 2);
	for (int i = 0; i < remain / 2; i++) {
		sub.glyphIdArray[i] = file.Read2();
	}

	if (debug) {
		printf("cmap SubTable Format4\n");
		printf(" format=%d\n", sub.format);
		printf(" length=%d\n", sub.length);
		printf(" language=%d\n", sub.language);
		printf(" segCountX2=%d\n", sub.segCountX2);
		printf(" searchRange=%d\n", sub.searchRange);
		printf(" entrySelector=%d\n", sub.entrySelector);
		printf(" rangeShift=%d\n", sub.rangeShift);
		printf(" glyphIdArray[%d]\n", (int)sub.glyphIdArray.size());
	}
}

// ASCII というか1バイトコードのマップを作成する。
// asciimap は 0-255 確保されていること。
void
TTF_cmap::MakeASCIIMap(std::vector<uint>& asciimap)
{
	// ASCII + 1バイトコード
	for (int code = 1, end = asciimap.size(); code < end; code++) {
		asciimap[code] = Code2GID(code);
	}
	if (debug) {
		printf("asciimap:\n");
		for (int i = 0, end = asciimap.size(); i < end; i++) {
			printf(" %3d", asciimap[i]);
			if (i % 16 == 15) {
				printf("\n");
			}
		}
	}
}

// JIS コードのマップを作成する。
// jismap はあらかじめ確保されていること。
void
TTF_cmap::MakeJISMap(std::vector<uint>& jismap)
{
	auto cd = iconv_open("unicodebig", "iso-2022-jp");
	if (cd == (iconv_t)-1) {
		err(1, "iconv_open");
	}

	for (int h = 0; h < 0x5e; h++) {
		for (int l = 0; l < 0x5e; l++) {
			int idx = h * 0x5e + l;

			// JIS コードを Unicode コードポイントに変換
			int jh = 0x21 + h;
			int jl = 0x21 + l;
			std::string srcbuf = string_format("\x1b$B%c%c\x1b(B", jh, jl);
			const char *src = &srcbuf[0];
			size_t srcleft = srcbuf.length();
			std::array<uint8, 4> dstbuf;
			char *dst = (char *)dstbuf.data();
			size_t dstleft = dstbuf.size();
			auto r = ICONV(cd, &src, &srcleft, &dst, &dstleft);
			if (r == (size_t)-1) {
				// JIS コードの割り当てがないところでは EILSEQ になる
				if (errno == EILSEQ) {
					continue;
				}
				err(1, "iconv %02x%02x failed", jh, jl);
			}
			if (r != 0) {
				errx(1, "iconv: invalid conversions is %zd", r);
			}
			if (dstleft != 2) {
				errx(1, "iconv %02x%02x: dstleft=%zd", jh, jl, dstleft);
			}
			// UnicodeBig は2バイト。
			// ビッグエンディアンで取り出しているので上位、下位の順
			uint32 ucode = (dstbuf[0] << 8) | dstbuf[1];

			// JIS コードの {jh,jl} を Unicode に変換して GID を引く。
			auto gid = Code2GID(ucode);
			jismap[idx] = gid;
		}
	}
	iconv_close(cd);

	if (0 && debug) {
		printf("jismap:\n");
		for (int h = 0; h < 0x5e; h++) {
			for (int l = 0; l < 0x5e; l++) {
				int idx = h * 0x5e + l;
				int gid = jismap[idx];
				if (gid != 0) {
					printf(" JIS%02x%02x GID=%4d\n", h + 0x21, l + 0x21, gid);
				}
			}
		}
	}
}

// 文字コード code からグリフインデックス(GID)を求める
int
TTF_cmap::Code2GID(int code) const
{
	// endCode[] から code を含むセグメント i を求める
	int i = -1;
	for (int j = 0; j < sub.endCode.size(); j++) {
		if (code <= sub.endCode[j]) {
			i = j;
			break;
		}
	}
	if (i < 0) {
		return 0;
	}

	// このセグメントの開始コードより若ければ、対応インデックスなし
	int start = sub.startCode[i];
	if (code < start) {
		return 0;
	}

	if (sub.idRangeOffsets[i] == 0) {
		return (code + sub.idDelta[i]) & 0xffff;
	} else {
		errx(1, "idRangeOffset != 0");
	}
}


// BigGlyphMetrics を読み込む。file は更新される。
void
BigGlyphMetrics::Load(File& file)
{
	height			= file.Read1();
	width			= file.Read1();
	horiBearingX	= file.Read1();
	horiBearingY	= file.Read1();
	horiAdvance		= file.Read1();
	vertBearingX	= file.Read1();
	vertBearingY	= file.Read1();
	vertAdvance		= file.Read1();
}

// SbitLineMetrics を読み込む。file は更新される。
void
SbitLineMetrics::Load(File& file)
{
	ascender		= file.Read1();
	descender		= file.Read1();
	widthMax		= file.Read1();
	caretSlopeNumerator		= file.Read1();
	caretSlopeDenominator	= file.Read1();
	caretOffset		= file.Read1();
	minOriginSB		= file.Read1();
	minAdvanceSB	= file.Read1();
	maxBeforeBL		= file.Read1();
	minAfterBL		= file.Read1();
	pad1			= file.Read1();
	pad2			= file.Read1();
}

// NameRecord.NameID の名前を文字列で返す
/*static*/ std::string
NameRecord::GetNameIDStr(uint id)
{
	static const char * const names[] = {
		"Copyright notice",				// 0
		"Font Family name",				// 1
		"Font Subfamily name",			// 2
		"Unique font identifier",		// 3
		"Full font name",				// 4
		"Version string",				// 5
		"PostScript name",				// 6
		"Trademark",					// 7
		"Manufacturer Name",			// 8
		"Designer",						// 9
		"Description",					// 10
		"URL Vendor",					// 11
		"URL Designer",					// 12
		"License Description",			// 13
		"License Info",					// 14
		"Reserved",						// 15
		"Typographic Family name",		// 16
		"Typographic Subfamily name",	// 17
		"Compatible Full",				// 18
		"Sample Text",					// 19
		"PostScript CID findfont name",	// 20
		"WWS Family Name",				// 21
		"WWS Subfamily Name",			// 22
		"Light Background Palette",		// 23	
		"Dark Background Palette",		// 24
		"Variations PostScript Name Prefix",	// 25
	};
	if (id < countof(names)) {
		return names[id];
	} else {
		return "";
	}
}

// EncodingRecord のデバッグ表示用文字列を返す
std::string
EncodingRecord::ToString() const
{
	std::string str;

	str = string_format("platformID=%d", platformID);
	static const char * const platform_names[] = {
		"Unicode",
		"Macintosh",
		"ISO(deprecated)",
		"Windows",
		"Custom",
	};
	if (platformID < countof(platform_names)) {
		str += string_format("(%s)", platform_names[platformID]);
	}

	str += string_format(" encodingID=%d", encodingID);
	if (platformID == 0) {
		static const char * encoding_names_0[] = {
			"1.0",
			"1.1",
			"ISO/IEC 10646",
			"2.0 BMP",
			"2.0 full",
			"Variation Sequences",
			"full reportoire",
		};
		if (encodingID < countof(encoding_names_0)) {
			str += string_format("(%s)", encoding_names_0[encodingID]);
		}
	} else if (platformID == 3) {
		static const char * encoding_names_3[] = {
			"Symbol",
			"Unicode BMP",
			"ShiftJIS",
			"PRC",
			"Big5",
			"Wansung",
			"Johab",
			"Reserved",
			"Reserved",
			"Reserved",
			"Unicode full reportoire",
		};
		if (encodingID < countof(encoding_names_3)) {
			str += string_format("(%s)", encoding_names_3[encodingID]);
		}
	}

	return str;
}


[[noreturn]] static void
usage()
{
	errx(1, "usage: [-f family_name] infile.TTF outascii.BDF outkanji.BDF");
}

int
main(int ac, char *av[])
{
	int c;

	while ((c = getopt(ac, av, "f:")) != -1) {
		switch (c) {
		 case 'f':
			new_family_name = optarg;
			break;
		 default:
			usage();
			break;
		}
	}
	ac -= optind;
	av += optind;

	if (ac < 3) {
		usage();
	}

	File infile;
	infile.Open(av[0]);

	TTFFile ttf;
	ttf.Open(infile);

	File outfile1;
	outfile1.Create(av[1]);
	ttf.SaveBDFAscii(outfile1);

	File outfile2;
	outfile2.Create(av[2]);
	ttf.SaveBDFKanji(outfile2);

	return 0;
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.