From 07e6e67bb8d0036b87f1f12ad0c708c579183723 Mon Sep 17 00:00:00 2001 From: Fini Jastrow Date: Mon, 9 Sep 2024 09:26:25 +0200 Subject: [PATCH] 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 --- src/tables/cff.mjs | 12 ++++++------ src/types.mjs | 7 +++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/tables/cff.mjs b/src/tables/cff.mjs index c9c40ef2..b189c289 100755 --- a/src/tables/cff.mjs +++ b/src/tables/cff.mjs @@ -488,7 +488,7 @@ function gatherCFFTopDicts(data, start, cffIndex, strings, version) { const privateSize = version < 2 ? topDict.private[0] : 0; const privateOffset = version < 2 ? topDict.private[1] : 0; if (privateSize !== 0 && privateOffset !== 0) { - const privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings, version); + const privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings, 2); topDict._defaultWidthX = privateDict.defaultWidthX; topDict._nominalWidthX = privateDict.nominalWidthX; if (privateDict.subrs !== null && privateDict.subrs !== 0) { @@ -1284,7 +1284,7 @@ function parseCFFTable(data, start, font, opt) { if (header.formatMajor < 2 && topDict.private[0] !== 0) { const privateDictOffset = start + topDict.private[1]; - const privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects, header.formatMajor); + const privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects, 2); font.defaultWidthX = privateDict.defaultWidthX; font.nominalWidthX = privateDict.nominalWidthX; @@ -1562,9 +1562,8 @@ function makeCharStringsIndex(glyphs, version) { function makePrivateDict(attrs, strings, version) { const t = new table.Record('Private DICT', [ - {name: 'dict', type: 'DICT', value: {}} + {name: 'dict', type: 'DICT', value: makeDict(version > 1 ? PRIVATE_DICT_META_CFF2 : PRIVATE_DICT_META, attrs, strings)} ]); - t.dict = makeDict(version > 1 ? PRIVATE_DICT_META_CFF2 : PRIVATE_DICT_META, attrs, strings); return t; } @@ -1608,7 +1607,7 @@ function makeCFFTable(glyphs, options) { attrs.strokeWidth = topDictOptions.strokeWidth || 0; } - const privateAttrs = {}; + const privateAttrs = topDictOptions._privateDict || {}; const glyphNames = []; let glyph; @@ -1628,7 +1627,7 @@ function makeCFFTable(glyphs, options) { t.globalSubrIndex = makeGlobalSubrIndex(); t.charsets = makeCharsets(glyphNames, strings); t.charStringsIndex = makeCharStringsIndex(glyphs, cffVersion); - t.privateDict = makePrivateDict(privateAttrs, strings); + t.privateDict = makePrivateDict(privateAttrs, strings, 2); // Needs to come at the end, to encode all custom strings used in the font. t.stringIndex = makeStringIndex(strings); @@ -1644,6 +1643,7 @@ function makeCFFTable(glyphs, options) { attrs.encoding = 0; attrs.charStrings = attrs.charset + t.charsets.sizeOf(); attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf(); + attrs.private[0] = t.privateDict.sizeOf(); // Recreate the Top DICT INDEX with the correct offsets. topDict = makeTopDict(attrs, strings); diff --git a/src/types.mjs b/src/types.mjs index 6c1b241a..84ce30f5 100644 --- a/src/types.mjs +++ b/src/types.mjs @@ -855,6 +855,13 @@ encode.OPERAND = function(v, type) { for (let j = 0; j < enc1.length; j++) { d.push(enc1[j]); } + } else if (type === 'delta') { + for (let i = 0; i < v.length; i++) { + const enc1 = encode.NUMBER(v[i]); + for (let j = 0; j < enc1.length; j++) { + d.push(enc1[j]); + } + } } else { throw new Error('Unknown operand type ' + type); // FIXME Add support for booleans