Skip to content
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

Edit font infos metadata #2039

Draft
wants to merge 33 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1c6d395
Update font data (eg. customData openTypeHheaAscender)
ollimeier Feb 18, 2025
5556427
Revert "Update font data (eg. customData openTypeHheaAscender)"
ollimeier Feb 18, 2025
59a1985
First draft of customData for font sources
ollimeier Feb 18, 2025
ea32f3a
minor changes to title and table width
ollimeier Feb 18, 2025
09b3562
Adding a customData mapping: Fontra to UFO + update data.
ollimeier Feb 18, 2025
1329845
Delete font info custom data within UFO
ollimeier Feb 19, 2025
e587385
Raise NotImplementedError if someone tries adding a customData attrib…
ollimeier Feb 19, 2025
672ef0c
Further work on font source customData (formatters, unittests, etc.)
ollimeier Feb 19, 2025
00b3415
Removing fsSelection and fsType for now, because of "invalid type" + …
ollimeier Feb 19, 2025
93467e5
Adding fsType and fsSelection defaults
ollimeier Feb 20, 2025
5971f60
Add "created" customData
ollimeier Feb 20, 2025
27e6e9d
Adding GlyphsApp to UFO customData key mapping
ollimeier Feb 20, 2025
183c37b
fix fsSelection
ollimeier Feb 20, 2025
c0fa08c
Add BooleanFormatter + unittest + extend customDataNameMapping
ollimeier Feb 20, 2025
faf3105
Sort unknown customData to the end of the list
ollimeier Feb 20, 2025
b8bf6f0
Raise error if wrong value entered.
ollimeier Feb 20, 2025
0a03e11
Create formatters.js and move code.
ollimeier Feb 25, 2025
3e75444
Revert testdata customData keys + ufoInfoAttributesToRoundTrip
ollimeier Feb 25, 2025
bca7715
remove leftover 'customDataNameMapping'
ollimeier Feb 25, 2025
1d6d190
Move customDataNameMapping from utils.js to customData.js
ollimeier Feb 25, 2025
3344838
Remove ufoInfoPrefix in UI + add "not implemented" if key not in cust…
ollimeier Feb 25, 2025
638888c
Update customDataNameMapping
ollimeier Feb 25, 2025
f27e558
Fix typo
ollimeier Feb 25, 2025
0105b0c
Fix sorting of font source customData
ollimeier Feb 25, 2025
e202d2e
Replace NumberArrayFormatter with ArrayFormatter
ollimeier Feb 25, 2025
a5a7864
Use .slice() instead of .substring()
ollimeier Feb 25, 2025
c54dcac
Rename customData.js to font-info-data.js
ollimeier Feb 25, 2025
03b7045
Fix typo
ollimeier Feb 25, 2025
071e1cc
Remove getDescenderWinDefault
ollimeier Feb 25, 2025
67ce5d0
Replace PanoseArrayFormatter with FixedLengthArrayFormatter + adjust …
ollimeier Feb 25, 2025
6eef48f
Remove getFamilyNameDefault, because fontSource has no 'familyName'.
ollimeier Feb 25, 2025
4a39970
Edit comment
ollimeier Feb 26, 2025
8a3d777
customDataNameMapping 'default' must be function
ollimeier Feb 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/fontra/backends/designspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -2173,11 +2173,20 @@ def updateFontInfoFromFontSource(reader, fontSource):

fontInfo.guidelines = packGuidelines(fontSource.guidelines)

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

# delete custom data
for infoAttr in ufoInfoAttributesToRoundTrip:
customDataKey = f"{ufoInfoPrefix}{infoAttr}"
if customDataKey not in fontSource.customData.keys():
value = getattr(fontInfo, infoAttr, None)
if value is not None:
delattr(fontInfo, infoAttr)

reader.writeInfo(fontInfo)

lib = reader.readLib()
Expand Down
135 changes: 135 additions & 0 deletions src/fontra/client/core/font-info-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import {
ArrayFormatter,
BooleanFormatter,
FixedLengthArrayFormatter,
_NumberFormatter,
} from "./formatters.js";

function getAscenderDefault(fontSource = undefined) {
return fontSource.lineMetricsHorizontalLayout.ascender.value || 800;
}

function getDescenderDefault(fontSource = undefined) {
return fontSource.lineMetricsHorizontalLayout.descender.value || -200;
}

function getSubfamilyNameDefault(fontSource = undefined) {
return fontSource.name || "Subfamily Name";
}

function getStrikeoutPositionDefault(fontSource = undefined) {
return fontSource.lineMetricsHorizontalLayout.ascender.value / 2 || 250;
}

function getCreatedDefault() {
// Note: UTC might differ from your local time.
const date = new Date();

const YYYY = date.getUTCFullYear();
const MM = String(date.getUTCMonth() + 1).padStart(2, "0");
const DD = String(date.getUTCDate()).padStart(2, "0");

const HH = String(date.getUTCHours()).padStart(2, "0");
const mm = String(date.getUTCMinutes()).padStart(2, "0");
const SS = String(date.getUTCSeconds()).padStart(2, "0");

return `${YYYY}/${MM}/${DD} ${HH}:${mm}:${SS}`; // "YYYY/MM/DD HH:MM:SS"
}

export const customDataNameMapping = {
// vertical metrics values
openTypeHheaAscender: { default: getAscenderDefault, formatter: _NumberFormatter },
openTypeHheaDescender: { default: getDescenderDefault, formatter: _NumberFormatter },
openTypeHheaLineGap: { default: () => 0, formatter: _NumberFormatter },
openTypeOS2TypoAscender: { default: getAscenderDefault, formatter: _NumberFormatter },
openTypeOS2TypoDescender: {
default: getDescenderDefault,
formatter: _NumberFormatter,
},
openTypeOS2TypoLineGap: { default: () => 0, formatter: _NumberFormatter },
openTypeOS2WinAscent: { default: getAscenderDefault, formatter: _NumberFormatter },
openTypeOS2WinDescent: {
default: (fontSource) => getDescenderDefault(fontSource) * -1,
formatter: _NumberFormatter,
},
postscriptUnderlinePosition: { default: () => -100, formatter: _NumberFormatter },
postscriptUnderlineThickness: { default: () => 50, formatter: _NumberFormatter },
openTypeOS2StrikeoutPosition: {
default: getStrikeoutPositionDefault,
formatter: _NumberFormatter,
},
openTypeOS2StrikeoutSize: { default: () => 50, formatter: _NumberFormatter },
// name table entries
openTypeNameUniqueID: { default: () => "uniqueID Name ID 3" }, // Name ID 3
openTypeNameVersion: { default: () => "Version 1.0" }, // Name ID 7
openTypeNamePreferredFamilyName: { default: () => "Family Name" }, // Name ID 16
openTypeNamePreferredSubfamilyName: { default: getSubfamilyNameDefault }, // Name ID 17
openTypeNameCompatibleFullName: { default: () => "Compatible Full Name" }, // Name ID 18
openTypeNameWWSFamilyName: { default: () => "Family Name" }, // Name ID 21
openTypeNameWWSSubfamilyName: { default: getSubfamilyNameDefault }, // Name ID 22
// misc
openTypeOS2WeightClass: { default: () => 400, formatter: _NumberFormatter },
openTypeOS2WidthClass: { default: () => 5, formatter: _NumberFormatter },
openTypeHeadCreated: { default: getCreatedDefault }, // The timezone is UTC.
openTypeOS2Selection: { default: () => [], formatter: ArrayFormatter }, // 7 = Use Typo Metrics, 8 = has WWS name, https://github.com/fonttools/fonttools/blob/598b974f87f35972da24e96e45bd0176d18930a0/Lib/fontTools/ufoLib/__init__.py#L1889
openTypeOS2Type: { default: () => [3], formatter: ArrayFormatter }, // https://github.com/googlefonts/glyphsLib/blob/c4db6b981d577f456d64ebe9993818770e170454/Lib/glyphsLib/builder/custom_params.py#L1166
openTypeOS2Panose: {
default: () => [2, 11, 5, 2, 4, 5, 4, 2, 2, 4],
formatter: FixedLengthArrayFormatter(10),
}, // default: sans-serif
openTypeOS2FamilyClass: {
default: () => [8, 0],
formatter: FixedLengthArrayFormatter(2),
}, // Class ID 8 = Sans Serif, Subclass ID = 0: No Classification
openTypeOS2UnicodeRanges: { default: () => [], formatter: ArrayFormatter },
openTypeOS2CodePageRanges: { default: () => [], formatter: ArrayFormatter },
// Postscript Font Level Hints, // https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf
postscriptBlueValues: { default: () => [], formatter: ArrayFormatter },
postscriptOtherBlues: { default: () => [], formatter: ArrayFormatter },
postscriptFamilyBlues: { default: () => [], formatter: ArrayFormatter },
postscriptFamilyOtherBlues: { default: () => [], formatter: ArrayFormatter },
postscriptBlueScale: { default: () => 0.039625, formatter: _NumberFormatter },
postscriptBlueShift: { default: () => 1, formatter: _NumberFormatter },
postscriptBlueFuzz: { default: () => 1, formatter: _NumberFormatter },
postscriptStemSnapH: { default: () => [], formatter: ArrayFormatter },
postscriptStemSnapV: { default: () => [], formatter: ArrayFormatter },
postscriptForceBold: { default: () => false, formatter: BooleanFormatter },
// PostScript Specific Data
// postscriptFontName // NOTE: not in ufoInfoAttributesToRoundTrip
// postscriptFullName // NOTE: not in ufoInfoAttributesToRoundTrip
postscriptSlantAngle: { default: () => 0.0, formatter: _NumberFormatter },
postscriptUniqueID: { default: () => 0, formatter: _NumberFormatter },
postscriptWeightName: { default: () => "postscriptWeightName" },
postscriptIsFixedPitch: { default: () => false, formatter: BooleanFormatter }, // Indicates if the font is monospaced.
postscriptDefaultWidthX: { default: () => 0, formatter: _NumberFormatter },
postscriptNominalWidthX: { default: () => 0, formatter: _NumberFormatter },
postscriptDefaultCharacter: { default: () => "glyphName" }, // The name of the glyph that should be used as the default character in PFM files.
postscriptWindowsCharacterSet: { default: () => 0, formatter: _NumberFormatter },
// OpenType vhea Table Fields
// openTypeVheaVertTypoAscender // NOTE: part of lineMetricsVerMapping
// openTypeVheaVertTypoDescender // NOTE: part of lineMetricsVerMapping
openTypeVheaVertTypoLineGap: { default: () => 0, formatter: _NumberFormatter },
openTypeVheaCaretSlopeRise: { default: () => 0, formatter: _NumberFormatter },
openTypeVheaCaretSlopeRun: { default: () => 0, formatter: _NumberFormatter },
openTypeVheaCaretOffset: { default: () => 0, formatter: _NumberFormatter },
// OpenType hhea Table Fields
openTypeHheaCaretSlopeRise: { default: () => 0, formatter: _NumberFormatter },
openTypeHheaCaretSlopeRun: { default: () => 0, formatter: _NumberFormatter },
openTypeHheaCaretOffset: { default: () => 0, formatter: _NumberFormatter },
// OpenType OS/2 Table Fields
openTypeOS2SubscriptXSize: { default: () => 0, formatter: _NumberFormatter },
openTypeOS2SubscriptYSize: { default: () => 0, formatter: _NumberFormatter },
openTypeOS2SubscriptXOffset: { default: () => 0, formatter: _NumberFormatter },
openTypeOS2SubscriptYOffset: { default: () => 0, formatter: _NumberFormatter },
openTypeOS2SuperscriptXSize: { default: () => 0, formatter: _NumberFormatter },
openTypeOS2SuperscriptYSize: { default: () => 0, formatter: _NumberFormatter },
openTypeOS2SuperscriptXOffset: { default: () => 0, formatter: _NumberFormatter },
openTypeOS2SuperscriptYOffset: { default: () => 0, formatter: _NumberFormatter },
// OpenType OS/2 Table Fields
openTypeHeadLowestRecPPEM: { default: () => 6, formatter: _NumberFormatter }, // Smallest readable size in pixels.
openTypeHeadFlags: { default: () => [], formatter: ArrayFormatter },
};

// TODO: Based on ufoInfoAttributesToRoundTrip (designspace.py)
// "openTypeGaspRangeRecords", // TODO: This is more complex, please see: https://unifiedfontobject.org/versions/ufo3/fontinfo.plist/#opentype-gasp-table-fields
// "openTypeNameRecords", // TODO: This is more complex, please see: https://unifiedfontobject.org/versions/ufo3/fontinfo.plist/#name-record-format
69 changes: 69 additions & 0 deletions src/fontra/client/core/formatters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// TODO: Keep in mind, that we have NumberFormatter in ui-utils.js.
// _NumberFormatter is different because of:
// NumberFormatter.fromString(0) == undefined -> but should be 0
// NumberFormatter.fromString(true) == 1 -> but should be undefined
export const _NumberFormatter = {
toString: (value) => value.toString(),
fromString: (value) => {
if (typeof value === "number") {
return { value: value };
} else if (typeof value === "boolean" || !value) {
return { error: "not a number" };
}
const number = Number(value);
if (isNaN(number)) {
return { error: "not a number" };
} else {
return { value: number };
}
},
};

export const BooleanFormatter = {
toString: (value) => value.toString(),
fromString: (value) => {
if (typeof value === "boolean") {
return { value: value };
}
if (value.trim().toLowerCase() === "true") {
return { value: true };
} else if (value.trim().toLowerCase() === "false") {
return { value: false };
} else {
return { error: "not a boolean" };
}
},
};

export const ArrayFormatter = {
toString: (value, arrayLength) => {
if (!Array.isArray(value)) {
return { error: "not an array" };
}
if (arrayLength && value.length != arrayLength) {
return { error: `array length must be ${arrayLength}` };
}
return value.toString();
},
fromString: (value, arrayLength) => {
let array = [];
try {
array = JSON.parse("[" + value + "]");
} catch (e) {
return { error: e };
}
if (Array.isArray(array)) {
if (arrayLength && array.length != arrayLength) {
return { error: `array length must be ${arrayLength}` };
}
return { value: array };
} else {
return { error: "not an array" };
}
},
};

export const FixedLengthArrayFormatter = (arrayLength) => ({
toString: (value) => ArrayFormatter.toString(value, arrayLength),
fromString: (value) => ArrayFormatter.fromString(value, arrayLength),
});
1 change: 1 addition & 0 deletions src/fontra/client/core/ui-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ export const DefaultFormatter = {
},
};

// TODO: Move to formatters.js
export const NumberFormatter = {
toString: (value) => value.toString(),
fromString: (value) => {
Expand Down
2 changes: 1 addition & 1 deletion src/fontra/views/fontinfo/panel-axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -917,7 +917,7 @@ export function updateRemoveButton(list, buttons) {
});
}

function arraysEqual(arrayA, arrayB) {
export function arraysEqual(arrayA, arrayB) {
if (arrayA.length !== arrayB.length) {
return false;
}
Expand Down
Loading