Skip to content

Commit

Permalink
cff: Implement private DICT reading and writing for CFF and CFF2
Browse files Browse the repository at this point in the history
[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>
  • Loading branch information
Finii authored and Connum committed Sep 9, 2024
1 parent aabf3b8 commit 07e6e67
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 6 deletions.
12 changes: 6 additions & 6 deletions src/tables/cff.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -1608,7 +1607,7 @@ function makeCFFTable(glyphs, options) {
attrs.strokeWidth = topDictOptions.strokeWidth || 0;
}

const privateAttrs = {};
const privateAttrs = topDictOptions._privateDict || {};

const glyphNames = [];
let glyph;
Expand All @@ -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);
Expand All @@ -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);
Expand Down
7 changes: 7 additions & 0 deletions src/types.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 07e6e67

Please sign in to comment.