-
Notifications
You must be signed in to change notification settings - Fork 481
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add read / write support for CFF private DICT data #759
base: master
Are you sure you want to change the base?
Conversation
aeb3319
to
aea28e5
Compare
Thanks for putting in the work! |
The tests are running through fine - what problem are you encountering? And can you please add a test case for it? Thanks! |
I fixed it with the force push, there were some more bugs (in the original code) 😬 😉 I added another commit (84004b8) that translates the I did not write any tests, but tested this manually with |
(more comments are in the commit messages, where I would search for them later on ;) |
src/tables/cff.mjs
Outdated
@@ -308,6 +308,17 @@ function interpretDict(dict, meta, strings) { | |||
if (m.type === 'SID') { | |||
value = getCFFString(strings, value); | |||
} | |||
if (m.type === 'delta' && value !== null) { | |||
if (!Array.isArray(value) || value.length % 2 != 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fact I can not see in the spec that an odd number of values is forbidden. But of course it would not make any sense, a BlueZone that starts somewhere and ends nowhere? I believe I remember to have read sometime that there must always be an even number of numbers grr, an even amount of numbers; but I can not find a reference for that.
Maybe someone knows more and/or you like to remove the even/odd test. It's in princible only a safeguard against unusable delta arrays. 🤷♀️
84004b8
to
f33a89c
Compare
The change regarding the delta values might be something I'll have to keep in mind when further working on #701 Thank you! |
Can you please fix the linting issues so the tests run through? We should also really have tests for this, so that nothing breaks in the future! Thanks! |
[why] FamilyBlues has two Ops, probably a copy and paste leftover. And two Ops are missing. This is now in line with CFF and CFF2 fonts. Signed-off-by: Fini Jastrow <[email protected]>
[why] Tests (would) fail with FDArrayTest257.otf [how] Some sanity checks have been ommited, add them in and process the structures only if there is anything. Signed-off-by: Fini Jastrow <[email protected]>
[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 <[email protected]>
f33a89c
to
ccab03f
Compare
shure ... oopsi you forced a 2nd time ;) $ git push --force-with-lease
To https://github.com/Finii/opentype.js.git
! [rejected] feature/output-CFF-private-DICT -> feature/output-CFF-private-DICT (stale info)
error: failed to push some refs to 'https://github.com/Finii/opentype.js.git'
|
[why] At the moment the delta values are not de- or encoded. The delta format is a packed format to have the smallest possible values in the array of numbers (i.e. relative to the previous number). But usually users do not face this but are shown absolute values; in all other font specific applications. For example BlueValues [ 500, 550 ] // User sees the BlueZone is from 500 to 550 Encoded as [ 500, 50 ] opentype.js at the moment does not translate these deltas in any way and users must know this and use the inconvenient 'packed' encoding format. [how] Convert the read relative delta values to absolute coordinates in the blueValues (and other) properties, that users can interact with. On font encoding time the absolute coordinates are converted back to relative ones and encoded in the font file. [note] https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf The second and subsequent numbers in a delta are encoded as the difference between successive values. For example, an array a0, a1, ..., an would be encoded as: a0 (a1–a0) (a2–a1) ..., (an–a(n–1)) This is done because small numbers can be encoded with fewer bytes and the total aim is to reduce the size. Signed-off-by: Fini Jastrow <[email protected]>
ccab03f
to
ffcfd38
Compare
If you define 'this' I could probably throw something together ;-D |
[why] The StemSnapH and StemSnapV values are deltas, according to [1]. Also noticed that HERE we have deltas with an odd number of values, so the check for even number of elements is wrong. [how] Change data type and remove check on read/write. Adapt test. [1] https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf (CFF version 1.0, Table 23 'Private DICT Operators') Signed-off-by: Fini Jastrow <[email protected]>
Added test code that checks
and it turns out there is a problem with reproducing Code: it('does round trip CFF private DICT', function() {
const font = loadSync('./test/fonts/AbrilFatface-Regular.otf');
// from ttx:
// <Private>
// <BlueValues value="-10 0 476 486 700 711"/>
// <OtherBlues value="-250 -238"/>
// <BlueScale value="0.039625"/>
// <BlueShift value="7"/>
// <BlueFuzz value="1"/>
// <StdHW value="18"/>
// <StdVW value="186"/>
// <StemSnapH value="16 18 21"/>
// <StemSnapV value="120 186 205"/>
// <ForceBold value="0"/>
const checkerFunktion = function(inputFont) {
const privateDict = inputFont.tables.cff.topDict._privateDict;
assert.deepEqual(privateDict.blueValues, [-10, 0, 476, 486, 700, 711]);
assert.deepEqual(privateDict.otherBlues, [-250, -238]);
assert.equal(privateDict.stdHW, 18);
assert.deepEqual(privateDict.stemSnapH, [16, 18, 21]);
assert.equal(privateDict.nominalWidthX, 590);
};
checkerFunktion(font);
let buffer = font.toArrayBuffer()
let font2 = parse(buffer);
checkerFunktion(font2);
});
|
The way we check other writing functionality is loading the produced font data and checking that the read-out data is as expected. |
[why] The code does not handle subrs, but it includes a previously existing subrs operator in the private DICT which throws all the offsets off. [how] Either include the subrs subtable and keep the subrs operator, or drop both. This commits just drops the operator (for simplicity). Proper subrs handling can be added later on. Signed-off-by: Fini Jastrow <[email protected]>
[why] Writing of the private DICT has been added together with the translation of delta values from transport encoding to readable and back. But there is no automated test to see if something breaks. [how] Take one of the fonts in test/fonts/ and run in through ttx to see its private DICT data analyzed by someone else. Add a test that also reads that fond and checks some of the private DICT values. Write the font into a buffer and read it back in; repeat the check on the read back font. This also checks if writing is correct. Signed-off-by: Fini Jastrow <[email protected]>
Fix private DICT in the presence of Then introduce a test for what has been added; checking both reading and writing (by re-reading). Taken from [1], added in edit [1] https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf |
[why] While CFF has no `vsindex` operator in the private DICT it is a defaulted operator in CFF2. This can not be really disentangled with the current code and the opertor ends up erroreously written to CFF fonts (where it is usually ignored). [how] It took a bit time to understand the CFF version code (that I believe is not finished), but reinstate that code with different tables to be used. Sorry I messed with the code in the first place; I had overlooked the `vsindex` operator and how a defaulted operator in the tables turn out in the written files. I just reverted the version 'detection' code as it was, not checking anything. Note that there still is one hard-coded `2` somewhere, but that was already there when I started. Signed-off-by: Fini Jastrow <[email protected]>
Did manual check on different machine.
$ git clone https://github.com/opentypejs/opentype.js.git
$ cd opentype.js
$ git remote add fork https://github.com/Finii/opentype.js.git
$ git fetch fork
$ npm install
$ npm run build
$ node
> const { default: opentype } = await import("./dist/opentype.js");
> const { default: fs } = await import("fs");
> let font = opentype.parse(fs.readFileSync('./test/fonts/AbrilFatface-Regular.otf'));
> fs.writeFileSync('abril.otf', Buffer.from(font.toArrayBuffer()));
> .exit
$ git checkout feature/output-CFF-private-DICT
$ npm run build
$ node
> const { default: opentype } = await import("./dist/opentype.js");
> const { default: fs } = await import("fs");
> let font = opentype.parse(fs.readFileSync('./test/fonts/AbrilFatface-Regular.otf'));
> fs.writeFileSync('abril2.otf', Buffer.from(font.toArrayBuffer()));
> .exit
$ ttx abril.otf abril2.otf
$ diff -u abril.ttx abril2.ttx --- abril.ttx 2024-09-10 13:08:45.540452926 +0200
+++ abril2.ttx 2024-09-10 13:08:45.466452833 +0200
@@ -433,12 +433,12 @@
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="1.0"/>
- <checkSumAdjustment value="0xffbb1068"/>
+ <checkSumAdjustment value="0x88bcf41f"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
- <created value="Tue Sep 10 11:05:57 2024"/>
- <modified value="Tue Sep 10 11:05:57 2024"/>
+ <created value="Tue Sep 10 11:07:57 2024"/>
+ <modified value="Tue Sep 10 11:07:57 2024"/>
<xMin value="-181"/>
<yMin value="-291"/>
<xMax value="1131"/>
@@ -1071,15 +1071,21 @@
<!-- charset is dumped separately as the 'GlyphOrder' element -->
<Encoding name="StandardEncoding"/>
<Private>
+ <BlueValues value="-10 0 476 486 700 711"/>
+ <OtherBlues value="-250 -238"/>
<BlueScale value="0.039625"/>
<BlueShift value="7"/>
<BlueFuzz value="1"/>
+ <StdHW value="18"/>
+ <StdVW value="186"/>
+ <StemSnapH value="16 18 21"/>
+ <StemSnapV value="120 186 205"/>
<ForceBold value="0"/>
<LanguageGroup value="0"/>
<ExpansionFactor value="0.06"/>
<initialRandomSeed value="0"/>
- <defaultWidthX value="0"/>
- <nominalWidthX value="0"/>
+ <defaultWidthX value="500"/>
+ <nominalWidthX value="590"/>
</Private>
<CharStrings>
<CharString name=".notdef"> Then I did a comparsion original font versus --- AbrilFatface-Regular.ttx 2024-09-10 13:11:55.420692296 +0200
+++ ../../abril.ttx 2024-09-10 13:08:45.540452926 +0200
- <fontRevision value="1.001"/>
+ <fontRevision value="1.0"/>
- <xMaxExtent value="1131"/>
+ <xMaxExtent value="1489"/>
<panose>
- <bFamilyType value="2"/>
+ <bFamilyType value="0"/>
<bSerifStyle value="0"/>
- <bWeight value="5"/>
- <bProportion value="3"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
<bContrast value="0"/>
<bStrokeVariation value="0"/>
<bArmStyle value="0"/>
- <bLetterForm value="2"/>
+ <bLetterForm value="0"/>
<bMidline value="0"/>
- <bXHeight value="3"/>
+ <bXHeight value="0"/>
</panose>
<CFFFont name="AbrilFatface-Regular">
- <version value="001.001"/>
+ <version value="Version 1.001"/>
- <FontBBox value="-181 -291 1131 1058"/>
+ <FontBBox value="0 -291 1058 1141"/>
<Private>
- <BlueValues value="-10 0 476 486 700 711"/>
- <OtherBlues value="-250 -238"/>
<BlueScale value="0.039625"/>
<BlueShift value="7"/>
<BlueFuzz value="1"/>
- <StdHW value="18"/>
- <StdVW value="186"/>
- <StemSnapH value="16 18 21"/>
- <StemSnapV value="120 186 205"/>
<ForceBold value="0"/>
<LanguageGroup value="0"/>
<ExpansionFactor value="0.06"/>
<initialRandomSeed value="0"/>
- <defaultWidthX value="500"/>
- <nominalWidthX value="590"/>
- <Subrs>
- <CharString index="0">
...
</Private>
<CharStrings>
I guess the subrs have been included here
- <GPOS>
Completely removed Problems
At least Panose and the version numbers seem to be easily fixed. Is there an interest in a PR; then I can give it some attention. |
Panose
Seems to be a Edit: Panose fixed in Version
Edit: Version fix in |
For CFF fonts the private DICT is not read and not written.
Some preparatory work for private DICT reading has been done with the preparation for CFF2, but not finished.
And in fact is useful for CFF1 also.
Writing is needed to have hints (BlueValues) in CFF fonts.
Description
[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.
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
Motivation and Context
It came unexpected that opentype.js silently drops the hinting of CFF fonts it reads and writes out.
This happened here:
The commit mono font is crafted with probably fontlab, but the release files are downloaded through opentype.js - which drops the hinting.
THAT has been reported to me here, because fontforge automagically adds the most basic hinting if there is none at all:
How Has This Been Tested?
Took several CFF fonts with BlueValues, opened them with
opentype.js
and wrote the back.The resulting font file has then been manually (:grimacing:) compared with the originals via
ttx
andfontforge
.Well, the PS private aka private DICT.
Screenshots (if appropriate):
Types of changes
Checklist:
npm run test
and all tests passed green (including code styling checks).[3] I believe there is no documentation stub where I could add to. Please refer me if there is one.