Skip to content

Commit aea28e5

Browse files
committed
cff: Implement private DICT reading and writing for CFF and CFF2
[why] The hinting of CFF (1 and 2) Open Type fonts is usually entirely in their private DICT data. To preserve the hinting - which is needed to make the font rendering look good and as expected - the private DICT data needs to be read and written. [how] Parts of the needed code seem to be added already in preparation for CFF2. I guess there was a misunderstanding or a mixture of versions, but the private DICT did not change between CFF1 and CFF2, and we need to always use the PRIVATE_DICT_META_CFF2, the link given in its definition is the same as given with link [1] for CFF1. See also [2]. So firstly we always refer to 'version 2', as the previous code did also but only in one case. The OtherBlues and FamilyOtherBlues operators must occur after the BlueValues and FamilyBlues operators, respectively. This is implicitely guaranteed by the way the private DICT is set up and finally Object.keys() works. For writing the delta values the encoding function is missing and so that is added. encode.delta() just calls encode.number() repeatedly, figuratively speaking. Last but not least the correct private DICT length has to be set. [1] https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf [2] https://learn.microsoft.com/en-us/typography/opentype/otspec180/cff2#appendixD Signed-off-by: Fini Jastrow <ulf.fini.jastrow@desy.de>
1 parent a47a5f3 commit aea28e5

File tree

2 files changed

+13
-6
lines changed

2 files changed

+13
-6
lines changed

src/tables/cff.mjs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ function gatherCFFTopDicts(data, start, cffIndex, strings, version) {
488488
const privateSize = version < 2 ? topDict.private[0] : 0;
489489
const privateOffset = version < 2 ? topDict.private[1] : 0;
490490
if (privateSize !== 0 && privateOffset !== 0) {
491-
const privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings, version);
491+
const privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings, 2);
492492
topDict._defaultWidthX = privateDict.defaultWidthX;
493493
topDict._nominalWidthX = privateDict.nominalWidthX;
494494
if (privateDict.subrs !== null && privateDict.subrs !== 0) {
@@ -1284,7 +1284,7 @@ function parseCFFTable(data, start, font, opt) {
12841284

12851285
if (header.formatMajor < 2 && topDict.private[0] !== 0) {
12861286
const privateDictOffset = start + topDict.private[1];
1287-
const privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects, header.formatMajor);
1287+
const privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects, 2);
12881288
font.defaultWidthX = privateDict.defaultWidthX;
12891289
font.nominalWidthX = privateDict.nominalWidthX;
12901290

@@ -1562,9 +1562,8 @@ function makeCharStringsIndex(glyphs, version) {
15621562

15631563
function makePrivateDict(attrs, strings, version) {
15641564
const t = new table.Record('Private DICT', [
1565-
{name: 'dict', type: 'DICT', value: {}}
1565+
{name: 'dict', type: 'DICT', value: makeDict(version > 1 ? PRIVATE_DICT_META_CFF2 : PRIVATE_DICT_META, attrs, strings)}
15661566
]);
1567-
t.dict = makeDict(version > 1 ? PRIVATE_DICT_META_CFF2 : PRIVATE_DICT_META, attrs, strings);
15681567
return t;
15691568
}
15701569

@@ -1608,7 +1607,7 @@ function makeCFFTable(glyphs, options) {
16081607
attrs.strokeWidth = topDictOptions.strokeWidth || 0;
16091608
}
16101609

1611-
const privateAttrs = {};
1610+
const privateAttrs = topDictOptions._privateDict || {};
16121611

16131612
const glyphNames = [];
16141613
let glyph;
@@ -1628,7 +1627,7 @@ function makeCFFTable(glyphs, options) {
16281627
t.globalSubrIndex = makeGlobalSubrIndex();
16291628
t.charsets = makeCharsets(glyphNames, strings);
16301629
t.charStringsIndex = makeCharStringsIndex(glyphs, cffVersion);
1631-
t.privateDict = makePrivateDict(privateAttrs, strings);
1630+
t.privateDict = makePrivateDict(privateAttrs, strings, 2);
16321631

16331632
// Needs to come at the end, to encode all custom strings used in the font.
16341633
t.stringIndex = makeStringIndex(strings);
@@ -1644,6 +1643,7 @@ function makeCFFTable(glyphs, options) {
16441643
attrs.encoding = 0;
16451644
attrs.charStrings = attrs.charset + t.charsets.sizeOf();
16461645
attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf();
1646+
attrs.private[0] = t.privateDict.sizeOf();
16471647

16481648
// Recreate the Top DICT INDEX with the correct offsets.
16491649
topDict = makeTopDict(attrs, strings);

src/types.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,13 @@ encode.OPERAND = function(v, type) {
855855
for (let j = 0; j < enc1.length; j++) {
856856
d.push(enc1[j]);
857857
}
858+
} else if (type === 'delta') {
859+
for (let i = 0; i < v.length; i++) {
860+
const enc1 = encode.NUMBER(v[i]);
861+
for (let j = 0; j < enc1.length; j++) {
862+
d.push(enc1[j]);
863+
}
864+
}
858865
} else {
859866
throw new Error('Unknown operand type ' + type);
860867
// FIXME Add support for booleans

0 commit comments

Comments
 (0)