Introducing & Building OpenType Collections (OTCs)

News|Adobe Blogs|Dr. Ken Lunde 2014-01-27 14:22:03

I would like to use this opportunity to introduce two new things.

First, OpenType Collections. TrueType Collections have been around for many years, and are commonplace for OS-bundled fonts. What I am speaking of are 'sfnt' Collections that include a 'CFF ' (PostScript charstrings) table rather than a 'glyf' (TrueType charstrings) one. The advantage of an 'sfnt' Collection is that fonts that differ in minor ways can be combined into a single resource, which can provide substantial size savings.

Second, brand new AFDKO tools, in the form of two Python scripts, for building, breaking apart, and displaying a synopsis of an OTC's tables. These scripts were developed by our incredibly talented font tools engineer, Read Roberts, so all thanks should go to him for preparing them.

In terms of the new AFDKO tools, those who cannot wait for the next AFDKO release can immediately download and start experimenting with the two Python scripts, otf2otc.py and otc2otf.py.

The workhorse tool is otf2otc.py, which combines multiple OTFs into a single OTC. By default, tables are shared by the font instances only if they are binary-identical, but this can be overridden by using the "-t" command-line option, which takes a four-character 'sfnt' table tag and font index as its argument. The "-o" command-line option is used to specify the name of the OTC, in terms of its filename. What follows on the command line are the OTFs that are to be combined. The order of the specified OTFs is significant if the "-t" command-line option is used, because one must specify the index of the font, which begins at zero (0), among the OTFs that are specified on the command line.

Below are two real-world examples of using this tool to create new OTCs.

First, I will combine Kozuka Gothic Pr6N M (小塚ゴシックPr6N M; Adobe-Japan1-6; 23,058 CIDs) with Kozuka Gothic Pro M (小塚ゴシックPro M; Adobe-Japan1-4, 15,444 CIDs), being sure to override the behavior to specify that the 'CFF ' table of the former font resource is to be used for both font instances (the latter is a pure subset of the former):

%python otf2otc.py-t 'CFF '=0-o KozGo-Medium.ttc KozGoPr6N-Medium.otf KozGoPro-Medium.otf

Input fonts: ['KozGoPr6N-Medium.otf', 'KozGoPro-Medium.otf']

Done

Second, I will combine the four font resources that make up the Kamono Kana Logo Line family into a single OTC:

%python otf2otc.py-o LogoLineStd.ttc LogoLineStd-Light.otf LogoLineStd-Medium.otf LogoLineStd-Bold.otf LogoLineStd-Ultra.otf

Input fonts: ['LogoLineStd-Light.otf', 'LogoLineStd-Medium.otf', 'LogoLineStd-Bold.otf', 'LogoLineStd-Ultra.otf']

Done

The otc2otf.py tool serves two purposes. One, which is evident by its name, is to break apart a Collection into individual OTFs. This is performed by specifying the "-w" command-line option. The other purpose, which is performed when no command-line option is specified, displays the table checksums, offsets, and lengths for each of the font instances in an OTC. Below is output from the latter function when applied to the two OTCs that I built:

%python otc2otf.py KozGo-Medium.ttc

Input font: KozGo-Medium.ttc

Version: 65536. numFonts: 2.

font 0 offset: 20/0x00000014. KozGoPr6N-Medium

BASE checksum: 0x1B8E18D8, offset: 0x0000020C, length: 0x000000E4

CFF checksum: 0xFD079A52, offset: 0x000002F0, length: 0x00492C87

GPOS checksum: 0xD5C4A73C, offset: 0x00492F77, length: 0x000077E2

GSUB checksum: 0x38D14556, offset: 0x004A04FD, length: 0x00032FEE

OS/2 checksum: 0x7976A4DD, offset: 0x004F3115, length: 0x00000060

VORG checksum: 0xF3929E18, offset: 0x004F31D5, length: 0x000005B4

cmap checksum: 0xADA99534, offset: 0x004F3A51, length: 0x0003FFF5

head checksum: 0xFE4D2B32, offset: 0x00560F8C, length: 0x00000036

hhea checksum: 0x0A27539C, offset: 0x00560FF8, length: 0x00000024

hmtx checksum: 0x250ADA69, offset: 0x00561040, length: 0x000153BC

maxp checksum: 0x5A125000, offset: 0x00583B16, length: 0x00000006

name checksum: 0x3B0710AF, offset: 0x00583B22, length: 0x0000081A

post checksum: 0xFFB80032, offset: 0x00584B43, length: 0x00000020

vhea checksum: 0x08AC6487, offset: 0x00584B63, length: 0x00000024

vmtx checksum: 0xC22B5D00, offset: 0x00584BAB, length: 0x000158C4

font 1 offset: 272/0x00000110. KozGoPro-Medium

BASE checksum: 0x1B8E18D8, offset: 0x0000020C, length: 0x000000E4

CFF checksum: 0xFD079A52, offset: 0x000002F0, length: 0x00492C87

GPOS checksum: 0xFD8C8D34, offset: 0x0049A759, length: 0x00005DA4

GSUB checksum: 0xF2DCEFFD, offset: 0x004D34EB, length: 0x0001FC2A

OS/2 checksum: 0x7688A341, offset: 0x004F3175, length: 0x00000060

VORG checksum: 0x7DBD360A, offset: 0x004F3789, length: 0x000002C8

cmap checksum: 0xD184E967, offset: 0x00533A46, length: 0x0002D546

head checksum: 0xFFD060E4, offset: 0x00560FC2, length: 0x00000036

hhea checksum: 0x0BAC3334, offset: 0x0056101C, length: 0x00000024

hmtx checksum: 0x5657396B, offset: 0x005763FC, length: 0x0000D71A

maxp checksum: 0x3C545000, offset: 0x00583B1C, length: 0x00000006

name checksum: 0x5FDE60BF, offset: 0x0058433C, length: 0x00000807

post checksum: 0xFFB80032, offset: 0x00584B43, length: 0x00000020

vhea checksum: 0x0A46467A, offset: 0x00584B87, length: 0x00000024

vmtx checksum: 0xCFEBE768, offset: 0x0059A46F, length: 0x0000E0BA

Done

%python otc2otf.py LogoLineStd.ttc

Input font: LogoLineStd.ttc

Version: 65536. numFonts: 4.

font 0 offset: 28/0x0000001C. LogoLineStd-Light

BASE checksum: 0x1BA818DF, offset: 0x0000040C, length: 0x000000E8

CFF checksum: 0x1715F501, offset: 0x000005D8, length: 0x00007D10

GPOS checksum: 0x1646F381, offset: 0x00020602, length: 0x000009FE

GSUB checksum: 0x9B0A9A80, offset: 0x00022C14, length: 0x00000926

OS/2 checksum: 0x7529D1A9, offset: 0x0002353A, length: 0x00000060

VORG checksum: 0x0D0B3CF9, offset: 0x000236BA, length: 0x00000050

cmap checksum: 0xA1E5EC81, offset: 0x000237FA, length: 0x00000D88

head checksum: 0xFC3D0459, offset: 0x00024582, length: 0x00000036

hhea checksum: 0x0B4203A3, offset: 0x00024624, length: 0x00000024

hmtx checksum: 0xC1D77DDB, offset: 0x00024690, length: 0x0000039C

maxp checksum: 0x01A15000, offset: 0x00025500, length: 0x00000006

name checksum: 0xE1EDF545, offset: 0x00025506, length: 0x00000797

post checksum: 0xFFB80032, offset: 0x00027350, length: 0x00000020

vhea checksum: 0x09C7136C, offset: 0x00027370, length: 0x00000024

vmtx checksum: 0xC3E2E642, offset: 0x00027394, length: 0x00000632

font 1 offset: 280/0x00000118. LogoLineStd-Medium

BASE checksum: 0x1B8E18D8, offset: 0x000004F4, length: 0x000000E4

CFF checksum: 0xC61D636F, offset: 0x000082E8, length: 0x000080F7

GPOS checksum: 0xF654DD9C, offset: 0x00021000, length: 0x000009AE

GSUB checksum: 0x9B0A9A80, offset: 0x00022C14, length: 0x00000926

OS/2 checksum: 0x75F1D5A9, offset: 0x0002359A, length: 0x00000060

VORG checksum: 0x0D0B3D27, offset: 0x0002370A, length: 0x00000050

cmap checksum: 0xA1E5EC81, offset: 0x000237FA, length: 0x00000D88

head checksum: 0xFC3D0459, offset: 0x00024582, length: 0x00000036

hhea checksum: 0x0B4203A3, offset: 0x00024624, length: 0x00000024

hmtx checksum: 0xBCAA7581, offset: 0x00024A2C, length: 0x0000039C

maxp checksum: 0x01A15000, offset: 0x00025500, length: 0x00000006

name checksum: 0xDB4FBB0C, offset: 0x00025C9D, length: 0x0000079E

post checksum: 0xFFB80032, offset: 0x00027350, length: 0x00000020

vhea checksum: 0x09C7136C, offset: 0x00027370, length: 0x00000024

vmtx checksum: 0xC2BDD81E, offset: 0x000279C6, length: 0x00000632

font 2 offset: 532/0x00000214. LogoLineStd-Bold

BASE checksum: 0x1B8E18D8, offset: 0x000004F4, length: 0x000000E4

CFF checksum: 0x31BA2CEC, offset: 0x000103DF, length: 0x000080E4

GPOS checksum: 0xC9E3B98B, offset: 0x000219AE, length: 0x00000922

GSUB checksum: 0x9B0A9A80, offset: 0x00022C14, length: 0x00000926

OS/2 checksum: 0x76B9D7C9, offset: 0x000235FA, length: 0x00000060

VORG checksum: 0x0D0B3D17, offset: 0x0002375A, length: 0x00000050

cmap checksum: 0xA1E5EC81, offset: 0x000237FA, length: 0x00000D88

head checksum: 0xFC43045B, offset: 0x000245B8, length: 0x00000036

hhea checksum: 0x0B4703A2, offset: 0x00024648, length: 0x00000024

hmtx checksum: 0xAEC76614, offset: 0x00024DC8, length: 0x0000039C

maxp checksum: 0x01A15000, offset: 0x00025500, length: 0x00000006

name checksum: 0x959E0945, offset: 0x0002643B, length: 0x00000787

post checksum: 0xFFB80032, offset: 0x00027350, length: 0x00000020

vhea checksum: 0x09C7136C, offset: 0x00027370, length: 0x00000024

vmtx checksum: 0xC14AC4BE, offset: 0x00027FF8, length: 0x00000632

font 3 offset: 784/0x00000310. LogoLineStd-Ultra

BASE checksum: 0x1B8E18D8, offset: 0x000004F4, length: 0x000000E4

CFF checksum: 0xA164D643, offset: 0x000184C3, length: 0x0000813F

GPOS checksum: 0xD5BEBF8B, offset: 0x000222D0, length: 0x00000944

GSUB checksum: 0x9B0A9A80, offset: 0x00022C14, length: 0x00000926

OS/2 checksum: 0x7781D8C9, offset: 0x0002365A, length: 0x00000060

VORG checksum: 0x0D0A3D12, offset: 0x000237AA, length: 0x00000050

cmap checksum: 0xA1E5EC81, offset: 0x000237FA, length: 0x00000D88

head checksum: 0xFC3D045B, offset: 0x000245EE, length: 0x00000036

hhea checksum: 0x0B4103A2, offset: 0x0002466C, length: 0x00000024

hmtx checksum: 0xAA6F5FBD, offset: 0x00025164, length: 0x0000039C

maxp checksum: 0x01A15000, offset: 0x00025500, length: 0x00000006

name checksum: 0x01BFFDAC, offset: 0x00026BC2, length: 0x0000078E

post checksum: 0xFFB80032, offset: 0x00027350, length: 0x00000020

vhea checksum: 0x09C7136C, offset: 0x00027370, length: 0x00000024

vmtx checksum: 0xC039BB2B, offset: 0x0002862A, length: 0x00000632

Done

Of course, the above output does not explicitly or outwardly specify which tables are shared, and which ones are not, so I wrote a short Perl script, called table-share-check.pl, which filters the output to display this information more clearly (note that the AFDKO spot tool emits similar information, and this filter handles that output equally as well). Below are the same two command lines as above, but filtered through this script:

%python otc2otf.py KozGo-Medium.ttc | perl table-share-check.pl

Shared Tables: BASE, CFF , post

Unshared Tables: GPOS, GSUB, OS/2, VORG, cmap, head, hhea, hmtx, maxp, name, vhea, vmtx

%python otc2otf.py LogoLineStd.ttc | perl table-share-check.pl

Shared Tables: GSUB, cmap, maxp, post, vhea

Unshared Tables: BASE, CFF , GPOS, OS/2, VORG, head, hhea, hmtx, name, vmtx

In terms of size savings, the sizes (in bytes) of the original OTFs and the OTCs that were built from them are shown below:

%ls-l KozGo*

-rw-r--r--1 lunde staff 5932329 Jan 27 12:02 KozGo-Medium.ttc

-rw-r--r--1 lunde staff 5476748 Jan 27 12:01 KozGoPr6N-Medium.otf

-rw-r--r--1 lunde staff 3481300 Jan 27 12:00 KozGoPro-Medium.otf

%ls-l LogoLine*

-rw-r--r--1 lunde staff 46408 Jan 27 12:01 LogoLineStd-Bold.otf

-rw-r--r--1 lunde staff 45668 Jan 27 12:01 LogoLineStd-Light.otf

-rw-r--r--1 lunde staff 46592 Jan 27 12:01 LogoLineStd-Medium.otf

-rw-r--r--1 lunde staff 46540 Jan 27 12:01 LogoLineStd-Ultra.otf

-rw-r--r--1 lunde staff 167004 Jan 27 12:03 LogoLineStd.ttc

Of course, these tools operate not only on CFF-based fonts, but also on TrueType-based ones, mainly because they are simply manipulating the 'sfnt' table structure.

As I alluded to earlier in this article, the next version of AFDKO will include these new scripts, but feel free to download them from this article in order to use them right away. And, in case it is not obvious, if any issues are found, please be sure to report them, such as a comment to this article.

In terms of OSes and applications that support OTCs, I have thus far confirmed OS X (I am running Version 10.9), its TextEdit app, Adobe InDesign (I am using CC, but I think that OTCs are supported from CS6), and Microsoft Excel (running on OS X).

UPDATE:Our friends at Apple just informed me that iOS7 supports OTCs.

Lastly, it seems that "ttc," not "otc," must be used as the filename extension for OpenType Collection fonts in order for them to be recognized.

Xplicit

Xplicit