-
-
Notifications
You must be signed in to change notification settings - Fork 111
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(developer): kmc-package support remote fonts and files
The concept here is that the 'Name' property for a file can now be a remote reference, rather than a local file. There are two supported formats in this commit: * GitHub: This is a cutdown version of a plain github.com URL, and must match this exact format: ``` github:<owner>/<repo>/raw/<hash>/<filepath/filename> ``` This format is mandated in order to ensure that we always have a hashed version of a file from the origin. This gives us reproducible builds, which avoids churn issues when font files change. Example: `github:silnrsi/fonts/raw/b88c7af5d16681bd137156929ff8baec82526560/fonts/sil/alkalami/Alkalami-Regular.ttf` gets https://github.com/silnrsi/fonts/raw/b88c7af5d16681bd137156929ff8baec82526560/fonts/sil/alkalami/Alkalami-Regular.ttf An alternative could be to just have `https://github.com/silnrsi/fonts/raw/b88c7af5d16681bd137156929ff8baec82526560/fonts/sil/alkalami/Alkalami-Regular.ttf` which could be matched with a regex in the same way as the `github` prefix, and would avoid the need to munge the input URL. **Discuss!** * fonts.languagetechnology.org: references just a font identifier. This is somewhat broken, because if the source file changes, we don't know about it and won't publish an updated version of the package. So this needs some more discussion (we could e.g. embed the version number in the request, e.g. `flo:[email protected]`). **Discuss!** ``` flo:<family> ``` e.g. `flo:andika` gets https://fonts.languagetechnology.org/fonts/sil/andika/Andika-Bold.ttf Future sources could be considered, e.g. noto. We don't want to allow arbitrary URLs, both for stability and for security reasons. This change is entirely compiler-side, so we don't need to make any changes to apps, and so packages will be backwardly compatible. A lot of work will need to be done with the Package Editor in TIKE to support this feature. Fixes: #11236
- Loading branch information
Showing
16 changed files
with
8,965 additions
and
90 deletions.
There are no files selected for viewing
131 changes: 131 additions & 0 deletions
131
developer/src/kmc-package/src/compiler/get-file-data.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
/* | ||
* Keyman is copyright (C) SIL Global. MIT License. | ||
* | ||
* Created by mcdurdin on 2024-11-11 | ||
*/ | ||
import { CompilerCallbacks } from '@keymanapp/developer-utils'; | ||
import { PackageCompilerMessages } from './package-compiler-messages.js'; | ||
|
||
const FLO_SOURCE = /^flo:(?<family>.+)$/; | ||
const GITHUB_SOURCE = /^github:(?<name>[a-zA-Z0-9-].+)\/(?<repo>[\w\.-]+)\/raw\/(?<hash>[a-f0-9]{40})\/(?<path>.+)$/; | ||
|
||
export interface KmpCompilerFileDataResult { | ||
data: Uint8Array; | ||
basename: string; | ||
}; | ||
|
||
let floFamiliesCache: any = undefined; // set to null on error, otherwise an object from JSON | ||
|
||
const FLO_FAMILIES_URL = 'https://fonts.languagetechnology.org/families.json'; | ||
|
||
async function getFloFamilies(callbacks: CompilerCallbacks) { | ||
if(floFamiliesCache === undefined) { | ||
try { | ||
floFamiliesCache = await callbacks.net.fetchJSON(FLO_FAMILIES_URL); | ||
/* c8 ignore next 12 */ | ||
if(!floFamiliesCache) { | ||
callbacks.reportMessage(PackageCompilerMessages.Error_FloDataCouldNotBeRead({url: FLO_FAMILIES_URL})); | ||
floFamiliesCache = null; | ||
} | ||
else if(typeof floFamiliesCache != 'object') { | ||
callbacks.reportMessage(PackageCompilerMessages.Error_FloDataIsInvalidFormat({url: FLO_FAMILIES_URL})); | ||
floFamiliesCache = null; | ||
} | ||
} catch(e) { | ||
callbacks.reportMessage(PackageCompilerMessages.Error_FloDataCouldNotBeRead({url: FLO_FAMILIES_URL, e})) | ||
floFamiliesCache = null; | ||
} | ||
} | ||
return floFamiliesCache; | ||
} | ||
|
||
async function getFileDataFromFlo(callbacks: CompilerCallbacks, _kpsFilename: string, inputFilename: string, matches: RegExpExecArray): Promise<KmpCompilerFileDataResult> { | ||
const floFamilies = await getFloFamilies(callbacks); | ||
/* c8 ignore next 4 */ | ||
if(!floFamilies) { | ||
// Error already reported by getFloFamilies | ||
return null; | ||
} | ||
|
||
const family = floFamilies[matches.groups.family]; | ||
if(!family) { | ||
callbacks.reportMessage(PackageCompilerMessages.Error_FontNotFoundInFlo({filename: inputFilename, family: matches.groups.family})); | ||
return null; | ||
} | ||
|
||
if(!family.distributable) { | ||
callbacks.reportMessage(PackageCompilerMessages.Warn_FontFromFloIsNotFreelyDistributable( | ||
{filename: inputFilename, family: matches.groups.family})); | ||
} | ||
|
||
// TODO: consider .woff, .woff2 for web font inclusion | ||
const ttf = family.defaults?.ttf; | ||
if(!ttf) { | ||
callbacks.reportMessage(PackageCompilerMessages.Error_FontInFloDoesNotHaveDefaultTtf({filename: inputFilename, family: matches.groups.family})); | ||
return null; | ||
} | ||
|
||
const file = family.files[ttf]; | ||
/* c8 ignore next 4 */ | ||
if(!file) { | ||
callbacks.reportMessage(PackageCompilerMessages.Error_FontInFloHasBrokenDefaultTtf({filename: inputFilename, family: matches.groups.family})); | ||
return null; | ||
} | ||
|
||
if(!file.flourl && !file.url) { | ||
callbacks.reportMessage(PackageCompilerMessages.Error_FontInFloHasNoDownloadAvailable({filename: inputFilename, family: matches.groups.family})); | ||
return null; | ||
} | ||
|
||
const url = file.flourl ?? file.url; | ||
try { | ||
const data = await callbacks.net.fetchBlob(url); | ||
if(!data) { | ||
callbacks.reportMessage(PackageCompilerMessages.Error_FontFileCouldNotBeDownloaded({filename: inputFilename, url})); | ||
return null; | ||
} | ||
return { data, basename: ttf }; | ||
/* c8 ignore next 4 */ | ||
} catch(e) { | ||
callbacks.reportMessage(PackageCompilerMessages.Error_FontFileCouldNotBeDownloaded({filename: inputFilename, url, e})); | ||
return null; | ||
} | ||
} | ||
|
||
async function getFileDataFromGitHub(callbacks: CompilerCallbacks, _kpsFilename: string, inputFilename: string, matches: RegExpExecArray): Promise<KmpCompilerFileDataResult> { | ||
// /^github:(?<name>[a-zA-Z0-9-].+)\/(?<repo>[\w\.-]+)\/raw\/(?<hash>[a-f0-9]{40})\/(?<path>.+)$/ | ||
const githubUrl = `https://github.com/${matches.groups.name}/${matches.groups.repo}/raw/${matches.groups.hash}/${matches.groups.path}`; | ||
try { | ||
const res = await callbacks.net.fetchBlob(githubUrl); | ||
if(!res) { | ||
callbacks.reportMessage(PackageCompilerMessages.Error_FontFileCouldNotBeDownloaded({filename: inputFilename, url: githubUrl})); | ||
return null; | ||
} | ||
return { data: res, basename: callbacks.path.basename(matches.groups.path) }; | ||
/* c8 ignore next 4 */ | ||
} catch(e) { | ||
callbacks.reportMessage(PackageCompilerMessages.Error_FontFileCouldNotBeDownloaded({filename: inputFilename, url: githubUrl, e})); | ||
return null; | ||
} | ||
} | ||
|
||
type KmpCompilerFileDataProc = (callbacks: CompilerCallbacks, kpsFilename: string, inputFilename: string, matches: RegExpExecArray) => Promise<KmpCompilerFileDataResult>; | ||
|
||
interface KmpCompilerFileDataSource { | ||
regex: RegExp; | ||
proc: KmpCompilerFileDataProc; | ||
}; | ||
|
||
export const EXTERNAL_FILE_DATA_SOURCES: KmpCompilerFileDataSource[] = [ | ||
{ regex: FLO_SOURCE, proc: getFileDataFromFlo }, | ||
{ regex: GITHUB_SOURCE, proc: getFileDataFromGitHub }, | ||
// TODO: noto | ||
]; | ||
|
||
/** @internal */ | ||
export const unitTestEndpoints = { | ||
GITHUB_SOURCE, | ||
getFileDataFromGitHub, | ||
FLO_SOURCE, | ||
getFileDataFromFlo, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.