|
|
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;
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.