Skip to content

Commit

Permalink
Merge pull request #1770 from googlefonts/ot-info-fields-issue-1769
Browse files Browse the repository at this point in the history
Round-trip selected UFO font info fields via FontSource.customData
  • Loading branch information
justvanrossum authored Nov 7, 2024
2 parents 5d707b5 + 765fe45 commit 60750b9
Show file tree
Hide file tree
Showing 20 changed files with 196 additions and 145 deletions.
88 changes: 86 additions & 2 deletions src/fontra/backends/designspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
# Fontra / UFO
"ascender": "openTypeVheaVertTypoAscender",
"descender": "openTypeVheaVertTypoDescender",
"lineGap": "openTypeVheaVertTypoLineGap",
"lineGap": "openTypeVheaVertTypoLineGap", # TODO: this doesn't really belong here
# ("slopeRise", "openTypeVheaCaretSlopeRise"),
# ("slopeRun", "openTypeVheaCaretSlopeRun"),
# ("caretOffset", "openTypeVheaCaretOffset"),
Expand All @@ -115,7 +115,79 @@
("manufacturerURL", "openTypeNameManufacturerURL"),
("licenseDescription", "openTypeNameLicense"),
("licenseInfoURL", "openTypeNameLicenseURL"),
("vendorID", "vendorID"),
("vendorID", "openTypeOS2VendorID"),
]


ufoInfoPrefix = "ufo.info."


ufoInfoAttributesToRoundTrip = [
"openTypeGaspRangeRecords",
"openTypeHeadCreated",
"openTypeHeadFlags",
"openTypeHeadLowestRecPPEM",
"openTypeHheaAscender",
"openTypeHheaCaretOffset",
"openTypeHheaCaretSlopeRise",
"openTypeHheaCaretSlopeRun",
"openTypeHheaDescender",
"openTypeHheaLineGap",
"openTypeNameCompatibleFullName",
"openTypeNamePreferredFamilyName",
"openTypeNamePreferredSubfamilyName",
"openTypeNameRecords",
"openTypeNameUniqueID",
"openTypeNameVersion",
"openTypeNameWWSFamilyName",
"openTypeNameWWSSubfamilyName",
"openTypeOS2CodePageRanges",
"openTypeOS2FamilyClass",
"openTypeOS2Panose",
"openTypeOS2Selection",
"openTypeOS2StrikeoutPosition",
"openTypeOS2StrikeoutSize",
"openTypeOS2SubscriptXOffset",
"openTypeOS2SubscriptXSize",
"openTypeOS2SubscriptYOffset",
"openTypeOS2SubscriptYSize",
"openTypeOS2SuperscriptXOffset",
"openTypeOS2SuperscriptXSize",
"openTypeOS2SuperscriptYOffset",
"openTypeOS2SuperscriptYSize",
"openTypeOS2Type",
"openTypeOS2TypoAscender",
"openTypeOS2TypoDescender",
"openTypeOS2TypoLineGap",
"openTypeOS2UnicodeRanges",
"openTypeOS2WeightClass",
"openTypeOS2WidthClass",
"openTypeOS2WinAscent",
"openTypeOS2WinDescent",
"openTypeVheaCaretOffset",
"openTypeVheaCaretSlopeRise",
"openTypeVheaCaretSlopeRun",
"openTypeVheaVertTypoLineGap",
"postscriptBlueFuzz",
"postscriptBlueScale",
"postscriptBlueShift",
"postscriptBlueValues",
"postscriptDefaultCharacter",
"postscriptDefaultWidthX",
"postscriptFamilyBlues",
"postscriptFamilyOtherBlues",
"postscriptForceBold",
"postscriptIsFixedPitch",
"postscriptNominalWidthX",
"postscriptOtherBlues",
"postscriptSlantAngle",
"postscriptStemSnapH",
"postscriptStemSnapV",
"postscriptUnderlinePosition",
"postscriptUnderlineThickness",
"postscriptUniqueID",
"postscriptWeightName",
"postscriptWindowsCharacterSet",
]


Expand Down Expand Up @@ -1429,6 +1501,7 @@ def locationTuple(self):
return locationToTuple(self.location)

def asFontraFontSource(self, unitsPerEm: int) -> FontSource:
customData = {}
if self.isSparse:
lineMetricsHorizontalLayout: dict[str, LineMetric] = {}
lineMetricsVerticalLayout: dict[str, LineMetric] = {}
Expand Down Expand Up @@ -1459,6 +1532,11 @@ def asFontraFontSource(self, unitsPerEm: int) -> FontSource:
guidelines = unpackGuidelines(fontInfo.guidelines)
italicAngle = getattr(fontInfo, "italicAngle", 0)

for infoAttr in ufoInfoAttributesToRoundTrip:
value = getattr(fontInfo, infoAttr, None)
if value is not None:
customData[f"{ufoInfoPrefix}{infoAttr}"] = value

return FontSource(
name=self.name,
location=self.location,
Expand All @@ -1467,6 +1545,7 @@ def asFontraFontSource(self, unitsPerEm: int) -> FontSource:
lineMetricsVerticalLayout=lineMetricsVerticalLayout,
guidelines=guidelines,
isSparse=self.isSparse,
customData=customData,
)

def asFontraGlyphSource(self, localDefaultOverride=None):
Expand Down Expand Up @@ -1914,6 +1993,11 @@ def updateFontInfoFromFontSource(reader, fontSource):

fontInfo.guidelines = packGuidelines(fontSource.guidelines)

for key, value in fontSource.customData.items():
if key.startswith(ufoInfoPrefix):
infoAttr = key[len(ufoInfoPrefix) :]
setattr(fontInfo, infoAttr, value)

reader.writeInfo(fontInfo)

lib = reader.readLib()
Expand Down
1 change: 1 addition & 0 deletions src/fontra/client/core/font-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,7 @@ function ensureDenseSources(sources) {
return { value: metric.value, zone: metric.zone || 0 };
}
),
customData: source.customData || {},
};
});
}
Expand Down
8 changes: 7 additions & 1 deletion src/fontra/client/core/font-sources-instancer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { DiscreteVariationModel } from "./discrete-variation-model.js";
import { LRUCache } from "./lru-cache.js";
import { areGuidelinesCompatible, normalizeGuidelines } from "./utils.js";
import {
areCustomDatasCompatible,
areGuidelinesCompatible,
normalizeGuidelines,
} from "./utils.js";
import { locationToString, mapAxesFromUserSpaceToSourceSpace } from "./var-model.js";

export class FontSourcesInstancer {
Expand Down Expand Up @@ -37,6 +41,7 @@ export class FontSourcesInstancer {

get deltas() {
const guidelinesAreCompatible = areGuidelinesCompatible(this.fontSourcesList);
const customDatasAreCompatible = areCustomDatasCompatible(this.fontSourcesList);

const fixedSourceValues = this.fontSourcesList.map((source) => {
return {
Expand All @@ -46,6 +51,7 @@ export class FontSourcesInstancer {
guidelines: guidelinesAreCompatible
? normalizeGuidelines(source.guidelines, true)
: [],
customData: customDatasAreCompatible ? source.customData : {},
};
});
return this.model.getDeltas(fixedSourceValues);
Expand Down
21 changes: 21 additions & 0 deletions src/fontra/client/core/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,27 @@ export function areGuidelinesCompatible(parents) {
return true;
}

export function areCustomDatasCompatible(parents) {
const referenceCustomData = parents[0].customData;
if (!referenceCustomData) {
return false;
}
const referenceKeys = Object.keys(referenceCustomData).sort();

for (const parent of parents.slice(1)) {
const keys = Object.keys(parent.customData).sort();
if (keys.length !== referenceKeys.length) {
return false;
}
for (const [kA, kB] of zip(keys, referenceKeys)) {
if (kA != kB) {
return false;
}
}
}
return true;
}

const identityGuideline = { x: 0, y: 0, angle: 0 };

export function normalizeGuidelines(guidelines, resetLocked = false) {
Expand Down
20 changes: 17 additions & 3 deletions src/fontra/core/instancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,9 +515,9 @@ def model(self):

@cached_property
def deltas(self):
guidelinesAreCompatible = areGuidelinesCompatible(
list(self.fontSourcesDense.values())
)
fontSourcesList = list(self.fontSourcesDense.values())
guidelinesAreCompatible = areGuidelinesCompatible(fontSourcesList)
customDatasAreCompatible = areCustomDatasCompatible(fontSourcesList)

fixedSourceValues = [
MathWrapper(
Expand All @@ -526,6 +526,7 @@ def deltas(self):
location={},
name="",
guidelines=source.guidelines if guidelinesAreCompatible else [],
customData=source.customData if customDatasAreCompatible else {},
)
)
for source in self.fontSourcesDense.values()
Expand Down Expand Up @@ -575,6 +576,19 @@ def areGuidelinesCompatible(parents):
return True


def areCustomDatasCompatible(parents):
if not parents:
return True # or False, doesn't matter

referenceKeys = parents[0].customData.keys()

for parent in parents[1:]:
if parent.customData.keys() != referenceKeys:
return False

return True


@dataclass
class MathWrapper:
subject: Any
Expand Down
1 change: 1 addition & 0 deletions src/fontra/workflow/merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def mapLocation(location):
| sourceB.lineMetricsHorizontalLayout,
lineMetricsVerticalLayout=sourceA.lineMetricsVerticalLayout
| sourceB.lineMetricsVerticalLayout,
customData=sourceA.customData | sourceB.customData,
)

return MergedSourcesInfo(
Expand Down
10 changes: 10 additions & 0 deletions test-js/test-font-sources-instancer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,28 @@ describe("FontSourcesInstancer Tests", () => {
location: { Weight: 400, Width: 50 },
verticalMetrics: { ascender: { value: 800 } },
guidelines: [{ name: "guide", x: 100, y: 200, angle: 0 }],
customData: {},
},
source2: {
name: "Bold",
location: { Weight: 900, Width: 50 },
verticalMetrics: { ascender: { value: 900 } },
guidelines: [],
customData: {},
},
source3: {
name: "Light Wide",
location: { Weight: 400, Width: 100 },
verticalMetrics: { ascender: { value: 850 } },
guidelines: [],
customData: {},
},
source4: {
name: "Bold Wide",
location: { Weight: 900, Width: 100 },
verticalMetrics: { ascender: { value: 950 } },
guidelines: [],
customData: {},
},
};

Expand All @@ -43,6 +47,7 @@ describe("FontSourcesInstancer Tests", () => {
location: { Weight: 400, Width: 50 },
verticalMetrics: { ascender: { value: 800 } },
guidelines: [{ name: "guide", x: 100, y: 200, angle: 0 }],
customData: {},
},
},
{
Expand All @@ -52,6 +57,7 @@ describe("FontSourcesInstancer Tests", () => {
location: { Weight: 400, Width: 50 },
verticalMetrics: { ascender: { value: 800 } },
guidelines: [{ name: "guide", x: 100, y: 200, angle: 0 }],
customData: {},
},
},
{
Expand All @@ -61,6 +67,7 @@ describe("FontSourcesInstancer Tests", () => {
location: { Weight: 900, Width: 50 },
verticalMetrics: { ascender: { value: 900 } },
guidelines: [],
customData: {},
},
},
{
Expand All @@ -70,6 +77,7 @@ describe("FontSourcesInstancer Tests", () => {
location: null,
verticalMetrics: { ascender: { value: 850 } },
guidelines: [],
customData: {},
},
},
{
Expand All @@ -79,6 +87,7 @@ describe("FontSourcesInstancer Tests", () => {
location: null,
verticalMetrics: { ascender: { value: 825 } },
guidelines: [],
customData: {},
},
},
{
Expand All @@ -88,6 +97,7 @@ describe("FontSourcesInstancer Tests", () => {
location: null,
verticalMetrics: { ascender: { value: 875 } },
guidelines: [],
customData: {},
},
},
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,6 @@
<string>License same as MutatorMath. BSD 3-clause. [test-token: A]</string>
<key>openTypeOS2VendorID</key>
<string>LTTR</string>
<key>postscriptBlueValues</key>
<array>
<integer>-16</integer>
<integer>0</integer>
<integer>500</integer>
<integer>516</integer>
<integer>800</integer>
<integer>816</integer>
</array>
<key>postscriptDefaultWidthX</key>
<integer>500</integer>
<key>postscriptFamilyBlues</key>
<array/>
<key>postscriptFamilyOtherBlues</key>
<array/>
<key>postscriptFontName</key>
<string>MutatorMathTest-BoldCondensed</string>
<key>postscriptFullName</key>
<string>MutatorMathTest BoldCondensed</string>
<key>postscriptOtherBlues</key>
<array>
<integer>-216</integer>
<integer>-200</integer>
</array>
<key>postscriptSlantAngle</key>
<integer>0</integer>
<key>postscriptStemSnapH</key>
<array/>
<key>postscriptStemSnapV</key>
<array/>
<key>postscriptWindowsCharacterSet</key>
<integer>1</integer>
<key>styleMapFamilyName</key>
<string></string>
<key>styleMapStyleName</key>
Expand Down
32 changes: 0 additions & 32 deletions test-py/data/mutatorsans/MutatorSansBoldWide.ufo/fontinfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,6 @@
<string>License same as MutatorMath. BSD 3-clause. [test-token: B]</string>
<key>openTypeOS2VendorID</key>
<string>LTTR</string>
<key>postscriptBlueValues</key>
<array>
<integer>-16</integer>
<integer>0</integer>
<integer>500</integer>
<integer>516</integer>
<integer>800</integer>
<integer>816</integer>
</array>
<key>postscriptDefaultWidthX</key>
<integer>500</integer>
<key>postscriptFamilyBlues</key>
<array/>
<key>postscriptFamilyOtherBlues</key>
<array/>
<key>postscriptFontName</key>
<string>MutatorMathTest-BoldWide</string>
<key>postscriptFullName</key>
<string>MutatorMathTest BoldWide</string>
<key>postscriptOtherBlues</key>
<array>
<integer>-216</integer>
<integer>-200</integer>
</array>
<key>postscriptSlantAngle</key>
<integer>0</integer>
<key>postscriptStemSnapH</key>
<array/>
<key>postscriptStemSnapV</key>
<array/>
<key>postscriptWindowsCharacterSet</key>
<integer>1</integer>
<key>styleMapFamilyName</key>
<string></string>
<key>styleMapStyleName</key>
Expand Down
Loading

0 comments on commit 60750b9

Please sign in to comment.