mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 15:52:05 +03:00
Fixes for Enso Font in GUI2 (#8508)
- Fixes issue reported in Discord. # Important Notes None
This commit is contained in:
parent
af50d32553
commit
f5c3713f87
1
app/gui2/.gitignore
vendored
1
app/gui2/.gitignore
vendored
@ -29,3 +29,4 @@ src/util/iconList.json
|
||||
src/util/iconName.ts
|
||||
src/stores/visualization/metadata.json
|
||||
public/font-*/
|
||||
src/assets/font-*.css
|
||||
|
@ -11,12 +11,6 @@
|
||||
crossorigin
|
||||
href="https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
crossorigin
|
||||
href="https://fonts.cdnfonts.com/css/dejavu-sans-mono"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Enso GUI</title>
|
||||
</head>
|
||||
|
@ -1,9 +1,22 @@
|
||||
/** @file ⚠️⚠️⚠️ THIS SCRIPT IS PROVIDED ONLY FOR CONVENIENCE. ⚠️⚠️⚠️
|
||||
* The sources of truth are at `build/build/src/project/gui2.rs` and
|
||||
* `build/build/src/ide/web/fonts.rs`. */
|
||||
|
||||
import * as fsSync from 'node:fs'
|
||||
import * as fs from 'node:fs/promises'
|
||||
import * as http from 'node:http'
|
||||
import * as https from 'node:https'
|
||||
import * as process from 'node:process'
|
||||
import tar from 'tar'
|
||||
import bz2 from 'unbzip2-stream'
|
||||
|
||||
if (process.env.CI === '1') process.exit(0)
|
||||
|
||||
const WARNING_MESSAGE =
|
||||
'⚠️⚠️⚠️ Please use the buildscript (`./run`) to download fonts instead. ⚠️⚠️⚠️'
|
||||
let warningMessageAlreadyShown = false
|
||||
let exitCode = 0
|
||||
|
||||
const ENSO_FONT_URL = 'https://github.com/enso-org/font/releases/download/1.0/enso-font-1.0.tar.gz'
|
||||
const MPLUS1_FONT_URL =
|
||||
'https://github.com/coz-m/MPLUS_FONTS/raw/71d438c798d063cc6fdae8d2864bc48f2d3d06ad/fonts/ttf/MPLUS1%5Bwght%5D.ttf'
|
||||
@ -12,11 +25,21 @@ const DEJAVU_SANS_MONO_FONT_URL =
|
||||
|
||||
/** @param {string | https.RequestOptions | URL} options
|
||||
* @param {((res: import('node:http').IncomingMessage) => void) | undefined} [callback] */
|
||||
function httpsGet(options, callback) {
|
||||
return https.get(options, (response) => {
|
||||
function get(options, callback) {
|
||||
const protocol =
|
||||
typeof options === 'string' ? new URL(options).protocol : options.protocol ?? 'https:'
|
||||
/** @type {{ get: typeof http['get'] }} */
|
||||
let httpModule = https
|
||||
switch (protocol) {
|
||||
case 'http:': {
|
||||
httpModule = http
|
||||
break
|
||||
}
|
||||
}
|
||||
return httpModule.get(options, (response) => {
|
||||
const location = response.headers.location
|
||||
if (location) {
|
||||
httpsGet(
|
||||
get(
|
||||
typeof options === 'string' || options instanceof URL
|
||||
? location
|
||||
: { ...options, ...new URL(location) },
|
||||
@ -28,51 +51,161 @@ function httpsGet(options, callback) {
|
||||
})
|
||||
}
|
||||
|
||||
console.info('Downloading Enso font...')
|
||||
await fs.rm('./public/font-enso/', { recursive: true, force: true })
|
||||
await fs.mkdir('./public/font-enso/', { recursive: true })
|
||||
await new Promise((resolve, reject) => {
|
||||
httpsGet(ENSO_FONT_URL, (response) => {
|
||||
response.pipe(
|
||||
tar.extract({
|
||||
cwd: './public/font-enso/',
|
||||
strip: 1,
|
||||
filter(path) {
|
||||
// Reject files starting with `.`.
|
||||
return !/[\\/][.]/.test(path)
|
||||
},
|
||||
}),
|
||||
)
|
||||
response.on('end', resolve)
|
||||
response.on('error', reject)
|
||||
})
|
||||
})
|
||||
console.info('Downloading M PLUS 1 font...')
|
||||
await fs.rm('./public/font-mplus1/', { recursive: true, force: true })
|
||||
await fs.mkdir('./public/font-mplus1/', { recursive: true })
|
||||
await new Promise((resolve, reject) => {
|
||||
httpsGet(MPLUS1_FONT_URL, (response) => {
|
||||
response.pipe(fsSync.createWriteStream('./public/font-mplus1/MPLUS1.ttf'))
|
||||
response.on('end', resolve)
|
||||
response.on('error', reject)
|
||||
})
|
||||
})
|
||||
console.info('Downloading DejaVu Sans Mono font...')
|
||||
await fs.rm('./public/font-dejavu/', { recursive: true, force: true })
|
||||
await fs.mkdir('./public/font-dejavu/', { recursive: true })
|
||||
await new Promise((resolve, reject) => {
|
||||
httpsGet(DEJAVU_SANS_MONO_FONT_URL, (response) => {
|
||||
response.pipe(bz2()).pipe(
|
||||
tar.extract({
|
||||
cwd: './public/font-dejavu/',
|
||||
strip: 2,
|
||||
filter(path) {
|
||||
return /[\\/]DejaVuSansMono/.test(path) || !/Oblique[.]ttf$/.test(path)
|
||||
},
|
||||
}),
|
||||
)
|
||||
response.on('end', resolve)
|
||||
response.on('error', reject)
|
||||
})
|
||||
})
|
||||
/** @param {unknown} error */
|
||||
function errorCode(error) {
|
||||
return typeof error === 'object' &&
|
||||
error != null &&
|
||||
'code' in error &&
|
||||
typeof error.code === 'string'
|
||||
? error.code
|
||||
: undefined
|
||||
}
|
||||
|
||||
/** @param {unknown} error */
|
||||
function isFileNotFoundError(error) {
|
||||
return errorCode(error) === 'ENOENT'
|
||||
}
|
||||
|
||||
const ENSO_FONT_VARIANTS = [
|
||||
{ variant: 'Thin', weight: 100 },
|
||||
{ variant: 'ExtraLight', weight: 200 },
|
||||
{ variant: 'Light', weight: 300 },
|
||||
{ variant: 'Regular', weight: 400 },
|
||||
{ variant: 'Medium', weight: 500 },
|
||||
{ variant: 'SemiBold', weight: 600 },
|
||||
{ variant: 'Bold', weight: 700 },
|
||||
{ variant: 'ExtraBold', weight: 800 },
|
||||
{ variant: 'Black', weight: 900 },
|
||||
].map((variant) => ({ font: 'Enso', ...variant }))
|
||||
|
||||
const DEJAVU_FONT_VARIANTS = [
|
||||
{ variant: 'DejaVuSansMono', weight: 400 },
|
||||
{ variant: 'DejaVuSansMono-Bold', weight: 700 },
|
||||
].map((variant) => ({ font: 'DejaVu Sans Mono', ...variant }))
|
||||
|
||||
try {
|
||||
await fs.access(`./src/assets/font-enso.css`)
|
||||
for (const { variant } of ENSO_FONT_VARIANTS) {
|
||||
await fs.access(`./public/font-enso/Enso-${variant}.ttf`)
|
||||
}
|
||||
console.info('Enso font already downloaded, skipping...')
|
||||
} catch (error) {
|
||||
if (!isFileNotFoundError(error)) {
|
||||
console.error('Unexpected error occurred when checking for Enso font:')
|
||||
console.error(error)
|
||||
exitCode = 1
|
||||
} else {
|
||||
if (!warningMessageAlreadyShown) console.warn(WARNING_MESSAGE)
|
||||
warningMessageAlreadyShown = true
|
||||
console.info('Downloading Enso font...')
|
||||
await fs.rm('./public/font-enso/', { recursive: true, force: true })
|
||||
await fs.mkdir('./public/font-enso/', { recursive: true })
|
||||
await new Promise((resolve, reject) => {
|
||||
get(ENSO_FONT_URL, (response) => {
|
||||
response.pipe(
|
||||
tar.extract({
|
||||
cwd: './public/font-enso/',
|
||||
strip: 1,
|
||||
filter(path) {
|
||||
// Reject files starting with `.`.
|
||||
return !/[\\/][.]/.test(path)
|
||||
},
|
||||
}),
|
||||
)
|
||||
response.on('end', resolve)
|
||||
response.on('error', reject)
|
||||
})
|
||||
})
|
||||
/** @type {string[]} */
|
||||
let css = []
|
||||
for (const { font, variant, weight } of ENSO_FONT_VARIANTS) {
|
||||
css.push(`\
|
||||
@font-face {
|
||||
font-family: '${font}';
|
||||
src: url('/font-enso/Enso-${variant}.ttf');
|
||||
font-weight: ${weight};
|
||||
}
|
||||
`)
|
||||
}
|
||||
await fs.writeFile('./src/assets/font-enso.css', css.join('\n'))
|
||||
}
|
||||
}
|
||||
try {
|
||||
await fs.access(`./src/assets/font-mplus1.css`)
|
||||
await fs.access(`./public/font-mplus1/MPLUS1[wght].ttf`)
|
||||
console.info('M PLUS 1 font already downloaded, skipping...')
|
||||
} catch (error) {
|
||||
if (!isFileNotFoundError(error)) {
|
||||
console.error('Unexpected error occurred when checking for M PLUS 1 font:')
|
||||
console.error(error)
|
||||
exitCode = 1
|
||||
} else {
|
||||
if (!warningMessageAlreadyShown) console.warn(WARNING_MESSAGE)
|
||||
warningMessageAlreadyShown = true
|
||||
console.info('Downloading M PLUS 1 font...')
|
||||
await fs.rm('./public/font-mplus1/', { recursive: true, force: true })
|
||||
await fs.mkdir('./public/font-mplus1/', { recursive: true })
|
||||
await new Promise((resolve, reject) => {
|
||||
get(MPLUS1_FONT_URL, (response) => {
|
||||
response.pipe(fsSync.createWriteStream('./public/font-mplus1/MPLUS1[wght].ttf'))
|
||||
response.on('end', resolve)
|
||||
response.on('error', reject)
|
||||
})
|
||||
})
|
||||
const css = `\
|
||||
@font-face {
|
||||
font-family: 'M PLUS 1';
|
||||
src: url('/font-mplus1/MPLUS1[wght].ttf');
|
||||
}
|
||||
`
|
||||
await fs.writeFile('./src/assets/font-mplus1.css', css)
|
||||
}
|
||||
}
|
||||
try {
|
||||
await fs.access(`./src/assets/font-dejavu.css`)
|
||||
for (const variant of ['', '-Bold']) {
|
||||
await fs.access(`./public/font-dejavu/DejaVuSansMono${variant}.ttf`)
|
||||
}
|
||||
console.info('DejaVu Sans Mono font already downloaded, skipping...')
|
||||
} catch (error) {
|
||||
if (!isFileNotFoundError(error)) {
|
||||
console.error('Unexpected error occurred when checking for DejaVu Sans Mono font:')
|
||||
console.error(error)
|
||||
exitCode = 1
|
||||
} else {
|
||||
if (!warningMessageAlreadyShown) console.warn(WARNING_MESSAGE)
|
||||
warningMessageAlreadyShown = true
|
||||
console.info('Downloading DejaVu Sans Mono font...')
|
||||
await fs.rm('./public/font-dejavu/', { recursive: true, force: true })
|
||||
await fs.mkdir('./public/font-dejavu/', { recursive: true })
|
||||
await new Promise((resolve, reject) => {
|
||||
get(DEJAVU_SANS_MONO_FONT_URL, (response) => {
|
||||
response.pipe(bz2()).pipe(
|
||||
tar.extract({
|
||||
cwd: './public/font-dejavu/',
|
||||
strip: 2,
|
||||
filter(path) {
|
||||
return /[\\/]DejaVuSansMono/.test(path) && !/Oblique[.]ttf$/.test(path)
|
||||
},
|
||||
}),
|
||||
)
|
||||
response.on('end', resolve)
|
||||
response.on('error', reject)
|
||||
})
|
||||
})
|
||||
/** @type {string[]} */
|
||||
let css = []
|
||||
for (const { font, variant, weight } of DEJAVU_FONT_VARIANTS) {
|
||||
css.push(`\
|
||||
@font-face {
|
||||
font-family: '${font}';
|
||||
src: url('/font-dejavu/${variant}.ttf');
|
||||
font-weight: ${weight};
|
||||
}
|
||||
`)
|
||||
}
|
||||
await fs.writeFile('./src/assets/font-dejavu.css', css.join('\n'))
|
||||
}
|
||||
}
|
||||
console.info('Done.')
|
||||
if (exitCode !== 0) process.exit(exitCode)
|
||||
|
@ -1,11 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'DejaVu Sans Mono';
|
||||
src: url('/font-dejavu/DejaVuSansMono.ttf');
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'DejaVu Sans Mono';
|
||||
src: url('/font-dejavu/DejaVuSansMono-Bold.ttf');
|
||||
font-weight: 700;
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'Enso';
|
||||
src: url('/font-enso/Enso-Thin.ttf');
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Enso';
|
||||
src: url('/font-enso/Enso-ExtraLight.ttf');
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Enso';
|
||||
src: url('/font-enso/Enso-Light.ttf');
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Enso';
|
||||
src: url('/font-enso/Enso-Regular.ttf');
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Enso';
|
||||
src: url('/font-enso/Enso-Medium.ttf');
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Enso';
|
||||
src: url('/font-enso/Enso-SemiBold.ttf');
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Enso';
|
||||
src: url('/font-enso/Enso-Bold.ttf');
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Enso';
|
||||
src: url('/font-enso/Enso-ExtraBold.ttf');
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Enso';
|
||||
src: url('/font-enso/Enso-Black.ttf');
|
||||
font-weight: 900;
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'M PLUS 1';
|
||||
src: url('/font-mplus1/MPLUS1.ttf');
|
||||
}
|
@ -247,6 +247,10 @@ const editorStyle = computed(() => {
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ͼ1 .cm-scroller) {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.resize-handle {
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
|
@ -167,7 +167,7 @@ function handleBreadcrumbClick(index: number) {
|
||||
--enso-docs-text-color: rbga(0, 0, 0, 0.6);
|
||||
--enso-docs-tag-background-color: #dcd8d8;
|
||||
--enso-docs-code-background-color: #dddcde;
|
||||
font-family: var(--font-code);
|
||||
font-family: var(--font-sans);
|
||||
font-size: 11.5px;
|
||||
line-height: 160%;
|
||||
color: var(--enso-docs-text-color);
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import url('../src/assets/font-enso-code.css');
|
||||
@import url('../src/assets/font-enso.css');
|
||||
|
||||
.histoire-story-viewer .__histoire-render-story > [data-v-app] {
|
||||
height: 100%;
|
||||
|
@ -17,6 +17,15 @@
|
||||
gui/:
|
||||
gui2/: # The new, Vue-based GUI.
|
||||
dist/:
|
||||
public/:
|
||||
font-enso/:
|
||||
font-mplus1/:
|
||||
font-dejavu/:
|
||||
src/:
|
||||
assets/:
|
||||
font-enso.css:
|
||||
font-mplus1.css:
|
||||
font-dejavu.css:
|
||||
ide-desktop/:
|
||||
lib/:
|
||||
client/:
|
||||
|
@ -25,6 +25,8 @@ use tracing::Span;
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub mod dejavu_font;
|
||||
pub mod enso_font;
|
||||
pub mod fonts;
|
||||
pub mod google_font;
|
||||
|
||||
|
134
build/build/src/ide/web/dejavu_font.rs
Normal file
134
build/build/src/ide/web/dejavu_font.rs
Normal file
@ -0,0 +1,134 @@
|
||||
//! Definitions for DejaVu fonts, and functions for downloading and installing them.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use enso_font as font;
|
||||
use enso_font::NonVariableDefinition;
|
||||
use enso_font::NonVariableFaceHeader;
|
||||
use ide_ci::cache::Cache;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
pub const PACKAGE_URL: &str = "https://github.com/dejavu-fonts/dejavu-fonts/releases/download/version_2_37/dejavu-fonts-ttf-2.37.zip";
|
||||
|
||||
const FONT_FAMILY: &str = "DejaVu Sans Mono";
|
||||
|
||||
const FILE_PREFIX: &str = "DejaVu";
|
||||
|
||||
const FILE_SANS_MONO_PREFIX: &str = "SansMono";
|
||||
|
||||
const SANS_MONO_FONT_FAMILY_FONTS: &[(&str, font::Weight)] =
|
||||
&[("-Bold", font::Weight::Bold), ("", font::Weight::Normal)];
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === DejaVu Font ===
|
||||
// ===================
|
||||
|
||||
/// Internal helper function to download the DejaVu Sans Mono font. Exposed via thin wrapper
|
||||
/// functions.
|
||||
async fn install_sans_mono_internal(
|
||||
cache: &Cache,
|
||||
octocrab: &Octocrab,
|
||||
output_path: impl AsRef<Path>,
|
||||
css_output_info: Option<(&str, impl AsRef<Path>)>,
|
||||
) -> Result {
|
||||
let output_path = output_path.as_ref();
|
||||
let font = font();
|
||||
let faces = faces();
|
||||
let font = crate::ide::web::fonts::filter_font(&font, &faces);
|
||||
let package = download(cache, octocrab).await?;
|
||||
let get_font_files = extract_fonts(&font, package, output_path);
|
||||
let make_css_file = crate::ide::web::fonts::write_css_file_if_required(
|
||||
FONT_FAMILY,
|
||||
&font,
|
||||
&faces,
|
||||
css_output_info,
|
||||
);
|
||||
try_join!(get_font_files, make_css_file)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Install DejaVu Sans Mono, without an auto-generated CSS file.
|
||||
pub async fn install_sans_mono(
|
||||
cache: &Cache,
|
||||
octocrab: &Octocrab,
|
||||
output_path: impl AsRef<Path>,
|
||||
) -> Result {
|
||||
install_sans_mono_internal(cache, octocrab, output_path, None::<(&str, &str)>).await
|
||||
}
|
||||
|
||||
/// Install DejaVu Sans Mono, including an auto-generated CSS file.
|
||||
pub async fn install_sans_mono_with_css(
|
||||
cache: &Cache,
|
||||
octocrab: &Octocrab,
|
||||
css_basepath: &str,
|
||||
output_path: impl AsRef<Path>,
|
||||
css_output_path: impl AsRef<Path>,
|
||||
) -> Result {
|
||||
install_sans_mono_internal(cache, octocrab, output_path, Some((css_basepath, css_output_path)))
|
||||
.await
|
||||
}
|
||||
|
||||
/// The DejaVu Sans Mono Font.
|
||||
pub fn font() -> NonVariableDefinition {
|
||||
SANS_MONO_FONT_FAMILY_FONTS
|
||||
.iter()
|
||||
.map(|(name, weight)| {
|
||||
let file = format!("{FILE_PREFIX}{FILE_SANS_MONO_PREFIX}{name}.ttf");
|
||||
let header = NonVariableFaceHeader {
|
||||
weight: *weight,
|
||||
width: font::Width::Normal,
|
||||
style: font::Style::Normal,
|
||||
};
|
||||
(header, file)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// All font faces contained in this font.
|
||||
pub fn faces() -> [NonVariableFaceHeader; 2] {
|
||||
[NonVariableFaceHeader { weight: font::Weight::Normal, ..default() }, NonVariableFaceHeader {
|
||||
weight: font::Weight::Bold,
|
||||
..default()
|
||||
}]
|
||||
}
|
||||
|
||||
/// Extract the fonts from the given archive file, and write them in the given directory.
|
||||
pub async fn extract_fonts(
|
||||
fonts: &NonVariableDefinition,
|
||||
package: impl AsRef<Path>,
|
||||
out_dir: impl AsRef<Path>,
|
||||
) -> Result {
|
||||
let mut archive = ide_ci::archive::zip::open(&package)?;
|
||||
crate::ide::web::fonts::extract_fonts(&mut archive, fonts, package, out_dir, &mut |path| {
|
||||
let mut iter = path.iter();
|
||||
for _ in iter.by_ref().take(2) {}
|
||||
Box::from(iter.as_str())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Download the DejaVu Font package, with caching and GitHub authentication.
|
||||
pub async fn download(cache: &Cache, octocrab: &Octocrab) -> Result<Box<Path>> {
|
||||
Ok(cache
|
||||
.get(ide_ci::cache::download::DownloadFile {
|
||||
client: octocrab.client.clone(),
|
||||
key: ide_ci::cache::download::Key {
|
||||
url: PACKAGE_URL.parse().unwrap(),
|
||||
additional_headers: reqwest::header::HeaderMap::from_iter([(
|
||||
reqwest::header::ACCEPT,
|
||||
reqwest::header::HeaderValue::from_static(
|
||||
mime::APPLICATION_OCTET_STREAM.as_ref(),
|
||||
),
|
||||
)]),
|
||||
},
|
||||
})
|
||||
.await?
|
||||
.into_boxed_path())
|
||||
}
|
113
build/build/src/ide/web/enso_font.rs
Normal file
113
build/build/src/ide/web/enso_font.rs
Normal file
@ -0,0 +1,113 @@
|
||||
//! Definitions for Enso Font, and functions for downloading and installing them.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use enso_enso_font::ttf;
|
||||
use enso_font::NonVariableFaceHeader;
|
||||
use ide_ci::cache::Cache;
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
const FONT_FAMILY: &str = "Enso";
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Enso Font ===
|
||||
// =================
|
||||
|
||||
pub async fn install_for_html(
|
||||
cache: &Cache,
|
||||
octocrab: &Octocrab,
|
||||
output_path: impl AsRef<Path>,
|
||||
) -> Result {
|
||||
let output_path = output_path.as_ref();
|
||||
let html_fonts: HashMap<_, _> = [
|
||||
(NonVariableFaceHeader { weight: ttf::Weight::Normal, ..default() }, "Regular"),
|
||||
(NonVariableFaceHeader { weight: ttf::Weight::ExtraBold, ..default() }, "Bold"),
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let html_font_definitions = enso_enso_font::font()
|
||||
.variations()
|
||||
.filter(|v| html_fonts.contains_key(&v.header))
|
||||
.collect();
|
||||
let get_font_files = async {
|
||||
let package = download(cache, octocrab).await?;
|
||||
enso_enso_font::extract_fonts(&html_font_definitions, package, output_path).await
|
||||
};
|
||||
let make_css_file = async {
|
||||
let mut css = String::new();
|
||||
let url = ".";
|
||||
for (header, variant) in html_fonts {
|
||||
use std::fmt::Write;
|
||||
let def = html_font_definitions.get(header);
|
||||
let def = def.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"Required font not found in Enso Font package. \
|
||||
Expected a font matching: {header:?}."
|
||||
)
|
||||
})?;
|
||||
let file = &def.file;
|
||||
// Note that this cannot use `generate_css_file`, as it specifies a different font
|
||||
// family for each variant.
|
||||
writeln!(&mut css, "@font-face {{")?;
|
||||
writeln!(&mut css, " font-family: '{FONT_FAMILY}{variant}';")?;
|
||||
writeln!(&mut css, " src: url('{url}/{file}');")?;
|
||||
writeln!(&mut css, " font-weight: normal;")?;
|
||||
writeln!(&mut css, " font-style: normal;")?;
|
||||
writeln!(&mut css, "}}")?;
|
||||
}
|
||||
let css_path = output_path.join("ensoFont.css");
|
||||
ide_ci::fs::tokio::write(css_path, css).await?;
|
||||
Ok(())
|
||||
};
|
||||
try_join!(get_font_files, make_css_file)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn install_with_css(
|
||||
cache: &Cache,
|
||||
octocrab: &Octocrab,
|
||||
css_basepath: &str,
|
||||
output_path: impl AsRef<Path>,
|
||||
css_output_path: impl AsRef<Path>,
|
||||
) -> Result {
|
||||
let output_path = output_path.as_ref();
|
||||
let font = enso_enso_font::font();
|
||||
let faces = enso_enso_font::faces();
|
||||
let font = crate::ide::web::fonts::filter_font(&font, &faces);
|
||||
let package = download(cache, octocrab).await?;
|
||||
let get_font_files = enso_enso_font::extract_fonts(&font, package, output_path);
|
||||
let css_output_info = Some((css_basepath, css_output_path));
|
||||
let make_css_file = crate::ide::web::fonts::write_css_file_if_required(
|
||||
FONT_FAMILY,
|
||||
&font,
|
||||
&faces,
|
||||
css_output_info,
|
||||
);
|
||||
try_join!(get_font_files, make_css_file)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Download the Enso Font package, with caching and GitHub authentication.
|
||||
pub async fn download(cache: &Cache, octocrab: &Octocrab) -> Result<Box<Path>> {
|
||||
Ok(cache
|
||||
.get(ide_ci::cache::download::DownloadFile {
|
||||
client: octocrab.client.clone(),
|
||||
key: ide_ci::cache::download::Key {
|
||||
url: enso_enso_font::PACKAGE_URL.parse().unwrap(),
|
||||
additional_headers: reqwest::header::HeaderMap::from_iter([(
|
||||
reqwest::header::ACCEPT,
|
||||
reqwest::header::HeaderValue::from_static(
|
||||
mime::APPLICATION_OCTET_STREAM.as_ref(),
|
||||
),
|
||||
)]),
|
||||
},
|
||||
})
|
||||
.await?
|
||||
.into_boxed_path())
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::ide::web::google_font;
|
||||
|
||||
use enso_enso_font::ttf;
|
||||
use enso_font::NonVariableDefinition;
|
||||
use enso_font::NonVariableFaceHeader;
|
||||
use ide_ci::archive::extract_files::ExtractFiles;
|
||||
use ide_ci::cache::Cache;
|
||||
|
||||
|
||||
@ -18,87 +17,176 @@ pub async fn install_html_fonts(
|
||||
output_path: impl AsRef<Path>,
|
||||
) -> Result {
|
||||
let output_path = output_path.as_ref();
|
||||
google_font::download_google_font(cache, octocrab, "mplus1", output_path).await?;
|
||||
install_enso_font_for_html(cache, octocrab, output_path).await?;
|
||||
crate::ide::web::google_font::install(cache, octocrab, "mplus1", output_path).await?;
|
||||
crate::ide::web::enso_font::install_for_html(cache, octocrab, output_path).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn install_enso_font_for_html(
|
||||
cache: &Cache,
|
||||
octocrab: &Octocrab,
|
||||
output_path: impl AsRef<Path>,
|
||||
) -> Result {
|
||||
let output_path = output_path.as_ref();
|
||||
let html_fonts: HashMap<_, _> = [
|
||||
(NonVariableFaceHeader { weight: ttf::Weight::Normal, ..default() }, "Regular"),
|
||||
(NonVariableFaceHeader { weight: ttf::Weight::ExtraBold, ..default() }, "Bold"),
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let html_font_definitions = enso_enso_font::enso_font()
|
||||
.variations()
|
||||
.filter(|v| html_fonts.contains_key(&v.header))
|
||||
.collect();
|
||||
let get_font_files = async {
|
||||
let package = get_enso_font_package_(cache, octocrab).await?;
|
||||
enso_enso_font::extract_fonts(&html_font_definitions, package, output_path).await
|
||||
};
|
||||
let make_css_file = async {
|
||||
let mut css = String::new();
|
||||
let family = "Enso";
|
||||
let url = ".";
|
||||
for (header, variant) in html_fonts {
|
||||
use std::fmt::Write;
|
||||
let def = html_font_definitions.get(header);
|
||||
let def = def.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"Required font not found in Enso Font package. \
|
||||
Expected a font matching: {header:?}."
|
||||
)
|
||||
})?;
|
||||
let file = &def.file;
|
||||
writeln!(&mut css, "@font-face {{")?;
|
||||
writeln!(&mut css, " font-family: '{family}{variant}';")?;
|
||||
writeln!(&mut css, " src: url('{url}/{file}');")?;
|
||||
writeln!(&mut css, " font-weight: normal;")?;
|
||||
writeln!(&mut css, " font-style: normal;")?;
|
||||
writeln!(&mut css, "}}")?;
|
||||
/// A CSS font style that is displayed as a CSS [`@font-face`] [`font-style`] value.
|
||||
///
|
||||
/// [`@font-face`]: https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face
|
||||
/// [`font-style`]: https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-style
|
||||
#[derive(Debug, Display, Copy, Clone)]
|
||||
pub enum FontStyle {
|
||||
#[display(fmt = "normal")]
|
||||
Normal,
|
||||
#[display(fmt = "italic")]
|
||||
Italic,
|
||||
#[display(fmt = "oblique")]
|
||||
Oblique,
|
||||
/// Angle is in degrees, between -90 and 90.
|
||||
#[display(fmt = "oblique {_0}deg")]
|
||||
ObliqueWithAngle(f64),
|
||||
/// Angles are in degrees, between -90 and 90.
|
||||
#[display(fmt = "oblique {_0}deg {_1}deg")]
|
||||
ObliqueWithAngleRange(f64, f64),
|
||||
}
|
||||
|
||||
/// A CSS font face that is displayed as a CSS [`@font-face`] declaration.
|
||||
///
|
||||
/// [`@font-face`]: https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FontFace<'a> {
|
||||
family: Cow<'a, str>,
|
||||
path: Cow<'a, str>,
|
||||
weight: Option<u16>,
|
||||
style: Option<FontStyle>,
|
||||
}
|
||||
|
||||
impl Display for FontFace<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let family = &self.family;
|
||||
let path = &self.path;
|
||||
writeln!(f, "@font-face {{")?;
|
||||
writeln!(f, " font-family: '{family}';")?;
|
||||
writeln!(f, " src: url('{path}');")?;
|
||||
if let Some(weight) = self.weight {
|
||||
writeln!(f, " font-weight: {weight};")?;
|
||||
}
|
||||
let css_path = output_path.join("ensoFont.css");
|
||||
ide_ci::fs::tokio::write(css_path, css).await?;
|
||||
if let Some(style) = self.style {
|
||||
writeln!(f, " font-style: {style};")?;
|
||||
}
|
||||
writeln!(f, "}}")?;
|
||||
Ok(())
|
||||
};
|
||||
try_join!(get_font_files, make_css_file)?;
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a CSS file containing the given font files. Does not include font-weight, so use this
|
||||
/// only if the font weights are not known - prefer [`generate_css_file`] in all other cases.
|
||||
pub fn generate_css_file_from_paths<AsRefStr>(
|
||||
basepath: &str,
|
||||
family: &str,
|
||||
paths: impl Iterator<Item = AsRefStr>,
|
||||
) -> Result<String>
|
||||
where
|
||||
AsRefStr: AsRef<str>,
|
||||
{
|
||||
let mut css = String::new();
|
||||
for path in paths {
|
||||
use std::fmt::Write;
|
||||
let path = format!("{basepath}/{}", path.as_ref());
|
||||
let font_face = FontFace {
|
||||
family: Cow::Borrowed(family),
|
||||
path: Cow::Borrowed(path.as_str()),
|
||||
weight: None,
|
||||
style: None,
|
||||
};
|
||||
writeln!(&mut css, "{font_face}")?;
|
||||
}
|
||||
Ok(css)
|
||||
}
|
||||
|
||||
/// Generate a CSS file containing the given font family, including only the given font variations.
|
||||
pub fn generate_css_file<'a>(
|
||||
basepath: &str,
|
||||
family: &str,
|
||||
definitions: &NonVariableDefinition,
|
||||
fonts: impl Iterator<Item = &'a NonVariableFaceHeader>,
|
||||
) -> Result<String> {
|
||||
let mut css = String::new();
|
||||
for header in fonts {
|
||||
use std::fmt::Write;
|
||||
let def = definitions.get(*header);
|
||||
let def = def.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"Required font not found in {family} Font package. \
|
||||
Expected a font matching: {header:?}."
|
||||
)
|
||||
})?;
|
||||
let path = format!("{basepath}/{}", def.file);
|
||||
let weight = def.header.weight.to_number();
|
||||
let font_face = FontFace {
|
||||
family: Cow::Borrowed(family),
|
||||
path: Cow::Borrowed(path.as_str()),
|
||||
weight: Some(weight),
|
||||
style: None,
|
||||
};
|
||||
writeln!(&mut css, "{font_face}")?;
|
||||
}
|
||||
Ok(css)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === Filter Font ===
|
||||
// ===================
|
||||
|
||||
pub fn filter_font(
|
||||
font: &NonVariableDefinition,
|
||||
faces: &[NonVariableFaceHeader],
|
||||
) -> NonVariableDefinition {
|
||||
font.variations().filter(|v| faces.contains(&v.header)).collect()
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === Make CSS File ===
|
||||
// =====================
|
||||
|
||||
pub async fn write_css_file_if_required(
|
||||
font_family: &str,
|
||||
font: &NonVariableDefinition,
|
||||
faces: &[NonVariableFaceHeader],
|
||||
css_output_info: Option<(&str, impl AsRef<Path>)>,
|
||||
) -> Result {
|
||||
if let Some((css_basepath, css_output_path)) = css_output_info {
|
||||
let contents = generate_css_file(css_basepath, font_family, font, faces.iter())?;
|
||||
ide_ci::fs::tokio::write(css_output_path, contents).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === Extract Fonts ===
|
||||
// =====================
|
||||
|
||||
/// Extract the fonts from the given archive file, and write them in the given directory.
|
||||
#[context("Failed to extract fonts from archive {}", package.as_ref().display())]
|
||||
pub async fn extract_fonts(
|
||||
archive: impl ExtractFiles,
|
||||
fonts: &NonVariableDefinition,
|
||||
package: impl AsRef<Path>,
|
||||
out_dir: impl AsRef<Path>,
|
||||
normalize_path: &mut impl FnMut(&Path) -> Box<str>,
|
||||
) -> Result {
|
||||
ide_ci::fs::tokio::create_dir_if_missing(out_dir.as_ref()).await?;
|
||||
let mut files_expected: HashSet<_> = fonts.files().collect();
|
||||
archive
|
||||
.extract_files(|path_in_archive| {
|
||||
let stripped_path = normalize_path(path_in_archive);
|
||||
if files_expected.remove(stripped_path.as_ref()) {
|
||||
Some(out_dir.as_ref().join(stripped_path.as_ref()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
ensure!(files_expected.is_empty(), "Required fonts not found in archive: {files_expected:?}.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Enso Font ===
|
||||
// =================
|
||||
|
||||
/// Download the Enso Font package, with caching and GitHub authentication.
|
||||
pub async fn get_enso_font_package() -> Result<Box<Path>> {
|
||||
let cache = Cache::new_default().await?;
|
||||
let octocrab = ide_ci::github::setup_octocrab().await?;
|
||||
get_enso_font_package_(&cache, &octocrab).await
|
||||
}
|
||||
|
||||
async fn get_enso_font_package_(cache: &Cache, octocrab: &Octocrab) -> Result<Box<Path>> {
|
||||
Ok(cache
|
||||
.get(ide_ci::cache::download::DownloadFile {
|
||||
client: octocrab.client.clone(),
|
||||
key: ide_ci::cache::download::Key {
|
||||
url: enso_enso_font::PACKAGE_URL.parse().unwrap(),
|
||||
additional_headers: reqwest::header::HeaderMap::from_iter([(
|
||||
reqwest::header::ACCEPT,
|
||||
reqwest::header::HeaderValue::from_static(
|
||||
mime::APPLICATION_OCTET_STREAM.as_ref(),
|
||||
),
|
||||
)]),
|
||||
},
|
||||
})
|
||||
.await?
|
||||
.into_boxed_path())
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! Downloading Google Fonts.
|
||||
//! Functions for downloading and installing Google fonts.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
@ -15,18 +15,18 @@ use octocrab::models::repos;
|
||||
// =================
|
||||
|
||||
/// Google Fonts repository.
|
||||
pub const GOOGLE_FONTS_REPOSITORY: RepoRef = RepoRef { owner: "google", name: "fonts" };
|
||||
pub const REPOSITORY: RepoRef = RepoRef { owner: "google", name: "fonts" };
|
||||
|
||||
/// Path to the directory on the Google Fonts repository where we get the fonts from.
|
||||
///
|
||||
/// The directory name denotes the license of the fonts. In our case this is SIL OPEN FONT LICENSE
|
||||
/// Version 1.1, commonly known as OFL.
|
||||
pub const GOOGLE_FONT_DIRECTORY: &str = "ofl";
|
||||
pub const DIRECTORY: &str = "ofl";
|
||||
|
||||
/// We keep dependency to a fixed commit, so we can safely cache it.
|
||||
///
|
||||
/// There are no known reasons not to bump this.
|
||||
pub const GOOGLE_FONT_SHA1: &str = "ea893a43af7c5ab5ccee189fc2720788d99887ed";
|
||||
pub const COMMIT_SHA1: &str = "ea893a43af7c5ab5ccee189fc2720788d99887ed";
|
||||
|
||||
|
||||
// ==============
|
||||
@ -51,7 +51,7 @@ impl Family {
|
||||
&self,
|
||||
handle: github::repo::Handle<impl IsRepo>,
|
||||
) -> Result<Vec<repos::Content>> {
|
||||
let path = format!("{GOOGLE_FONT_DIRECTORY}/{}", self.name);
|
||||
let path = format!("{DIRECTORY}/{}", self.name);
|
||||
let files = handle.repos().get_content().r#ref(&self.r#ref).path(path).send().await?;
|
||||
Ok(files.items.into_iter().filter(|file| file.name.ends_with(".ttf")).collect())
|
||||
}
|
||||
@ -137,17 +137,15 @@ impl Storable for DownloadFont {
|
||||
// === Entry Point ===
|
||||
// ===================
|
||||
|
||||
pub async fn download_google_font(
|
||||
/// Install a Google font, without an auto-generated CSS file.
|
||||
pub async fn install(
|
||||
cache: &Cache,
|
||||
octocrab: &Octocrab,
|
||||
family: &str,
|
||||
output_path: impl AsRef<Path>,
|
||||
) -> Result<Vec<PathBuf>> {
|
||||
let family = Family {
|
||||
repo: GOOGLE_FONTS_REPOSITORY.into(),
|
||||
r#ref: GOOGLE_FONT_SHA1.into(),
|
||||
name: family.into(),
|
||||
};
|
||||
let family =
|
||||
Family { repo: REPOSITORY.into(), r#ref: COMMIT_SHA1.into(), name: family.into() };
|
||||
let font = DownloadFont { family, octocrab: octocrab.clone() };
|
||||
let cached_fonts = cache.get(font).await?;
|
||||
let copy_futures =
|
||||
@ -156,6 +154,26 @@ pub async fn download_google_font(
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Install a Google font, including an auto-generated CSS file.
|
||||
pub async fn install_with_css(
|
||||
cache: &Cache,
|
||||
octocrab: &Octocrab,
|
||||
family: &str,
|
||||
css_family: &str,
|
||||
css_basepath: &str,
|
||||
output_path: impl AsRef<Path>,
|
||||
css_output_path: impl AsRef<Path>,
|
||||
) -> Result<Vec<PathBuf>> {
|
||||
let paths = install(cache, octocrab, family, output_path).await?;
|
||||
let css = crate::ide::web::fonts::generate_css_file_from_paths(
|
||||
css_basepath,
|
||||
css_family,
|
||||
paths.iter().flat_map(|path| path.try_file_name().map(|name| name.as_str())),
|
||||
)?;
|
||||
ide_ci::fs::tokio::write(css_output_path, css).await?;
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
@ -171,7 +189,7 @@ mod tests {
|
||||
let path = r"C:\temp\google_fonts2";
|
||||
let octocrab = ide_ci::github::setup_octocrab().await?;
|
||||
let cache = Cache::new_default().await?;
|
||||
let aaa = download_google_font(&cache, &octocrab, "mplus1", path).await?;
|
||||
let aaa = install(&cache, &octocrab, "mplus1", path).await?;
|
||||
dbg!(aaa);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -138,6 +138,32 @@ impl IsTarget for Gui2 {
|
||||
let WithDestination { inner: _, destination } = job;
|
||||
async move {
|
||||
let repo_root = &context.repo_root;
|
||||
crate::ide::web::google_font::install_with_css(
|
||||
&context.cache,
|
||||
&context.octocrab,
|
||||
"mplus1",
|
||||
"M PLUS 1",
|
||||
"/font-mplus1",
|
||||
&repo_root.app.gui_2.public.font_mplus_1,
|
||||
&repo_root.app.gui_2.src.assets.font_mplus_1_css,
|
||||
)
|
||||
.await?;
|
||||
crate::ide::web::dejavu_font::install_sans_mono_with_css(
|
||||
&context.cache,
|
||||
&context.octocrab,
|
||||
"/font-dejavu",
|
||||
&repo_root.app.gui_2.public.font_dejavu,
|
||||
&repo_root.app.gui_2.src.assets.font_dejavu_css,
|
||||
)
|
||||
.await?;
|
||||
crate::ide::web::enso_font::install_with_css(
|
||||
&context.cache,
|
||||
&context.octocrab,
|
||||
"/font-enso",
|
||||
&repo_root.app.gui_2.public.font_enso,
|
||||
&repo_root.app.gui_2.src.assets.font_enso_css,
|
||||
)
|
||||
.await?;
|
||||
crate::web::install(repo_root).await?;
|
||||
script(repo_root, Scripts::Build)?.run_ok().await?;
|
||||
ide_ci::fs::mirror_directory(
|
||||
|
@ -12,6 +12,7 @@ use tracing::Span;
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub mod extract_files;
|
||||
pub mod tar;
|
||||
pub mod zip;
|
||||
|
||||
|
11
build/ci_utils/src/archive/extract_files.rs
Normal file
11
build/ci_utils/src/archive/extract_files.rs
Normal file
@ -0,0 +1,11 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub trait ExtractFiles {
|
||||
/// The given function will be called with the path of each file within the archive. For each
|
||||
/// input path, if it returns a path the file will be extracted to the returned path.
|
||||
///
|
||||
/// IMPORTANT: If the function uses its input path to generate an output path, care must be
|
||||
/// taken that the output path is not in an unexpected location, especially if coming from an
|
||||
/// untrusted archive.
|
||||
async fn extract_files(self, filter: impl FnMut(&Path) -> Option<PathBuf>) -> Result;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::archive::extract_files::ExtractFiles;
|
||||
use flate2::read::GzDecoder;
|
||||
use std::fs::File;
|
||||
|
||||
@ -64,17 +65,6 @@ impl Archive {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The given function will be called with the path of each file within the archive. For each
|
||||
/// input path, if it returns a path the file will be extracted to the returned path.
|
||||
///
|
||||
/// IMPORTANT: If the function uses its input path to generate an output path, care must be
|
||||
/// taken that the output path is not in an unexpected location, especially if coming from an
|
||||
/// untrusted archive.
|
||||
pub async fn extract_files(self, filter: impl FnMut(&Path) -> Option<PathBuf>) -> Result {
|
||||
let job = move || self.extract_files_sync(filter);
|
||||
tokio::task::block_in_place(job)
|
||||
}
|
||||
|
||||
/// Extract all files from the specified subtree in the archive, placing them in the specified
|
||||
/// output directory.
|
||||
pub async fn extract_subtree(
|
||||
@ -106,3 +96,10 @@ impl Archive {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractFiles for Archive {
|
||||
async fn extract_files(self, filter: impl FnMut(&Path) -> Option<PathBuf>) -> Result {
|
||||
let job = move || self.extract_files_sync(filter);
|
||||
tokio::task::block_in_place(job)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::archive::extract_files::ExtractFiles;
|
||||
use anyhow::Context;
|
||||
use std::io::Cursor;
|
||||
use zip::read::ZipFile;
|
||||
@ -68,3 +69,43 @@ pub fn extract_subtree(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Synchronous version of [`extract_files`].
|
||||
#[context("Failed to extract files from ZIP archive")]
|
||||
pub fn extract_files_sync(
|
||||
archive: &mut ZipArchive<std::fs::File>,
|
||||
mut filter: impl FnMut(&Path) -> Option<PathBuf>,
|
||||
) -> Result {
|
||||
for i in 0..archive.len() {
|
||||
let mut entry = archive.by_index(i).with_context(|| "Error getting ZIP archive entry")?;
|
||||
let path_in_archive = entry
|
||||
.enclosed_name()
|
||||
.with_context(|| "Could not get file path of ZIP archive entry")?;
|
||||
if let Some(output_path) = filter(path_in_archive) {
|
||||
let entry_type = if entry.is_dir() { "directory" } else { "file" };
|
||||
let make_message = |prefix, path: &Path| {
|
||||
format!(
|
||||
"{} {:?} entry: {} => {}",
|
||||
prefix,
|
||||
entry_type,
|
||||
path.display(),
|
||||
output_path.display()
|
||||
)
|
||||
};
|
||||
|
||||
trace!("{}", make_message("Extracting", path_in_archive));
|
||||
let mut output = std::fs::File::create(&output_path)
|
||||
.with_context(|| make_message("Could not extract file", path_in_archive))?;
|
||||
std::io::copy(&mut entry, &mut output)
|
||||
.with_context(|| format!("Could not copy file to {}", output_path.display()))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl ExtractFiles for &mut ZipArchive<std::fs::File> {
|
||||
async fn extract_files(self, filter: impl FnMut(&Path) -> Option<PathBuf>) -> Result {
|
||||
let job = move || extract_files_sync(self, filter);
|
||||
tokio::task::block_in_place(job)
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
#![feature(pin_macro)]
|
||||
#![feature(result_option_inspect)]
|
||||
#![feature(extend_one)]
|
||||
#![feature(async_fn_in_trait)]
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
@ -35,6 +36,7 @@
|
||||
#![warn(trivial_numeric_casts)]
|
||||
#![warn(unused_import_braces)]
|
||||
#![warn(unused_qualifications)]
|
||||
#![allow(incomplete_features)]
|
||||
|
||||
|
||||
// ==============
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
// === Features ===
|
||||
#![feature(let_chains)]
|
||||
#![feature(default_free_fn)]
|
||||
// === Standard Linter Configuration ===
|
||||
#![deny(non_ascii_idents)]
|
||||
#![warn(unsafe_code)]
|
||||
@ -36,9 +37,9 @@ pub use owned_ttf_parser as ttf;
|
||||
// =================
|
||||
|
||||
/// The name of the Enso font family.
|
||||
pub const ENSO_FONT_FAMILY_NAME: &str = "enso";
|
||||
pub const FONT_FAMILY: &str = "enso";
|
||||
|
||||
const ENSO_FONT_FAMILY_FONTS: &[(&str, ttf::Weight)] = &[
|
||||
const FONTS: &[(&str, ttf::Weight)] = &[
|
||||
("Black", ttf::Weight::Black),
|
||||
("Bold", ttf::Weight::Bold),
|
||||
("ExtraBold", ttf::Weight::ExtraBold),
|
||||
@ -67,9 +68,9 @@ pub mod feature {
|
||||
// === Enso Font ===
|
||||
// =================
|
||||
|
||||
/// Returns the Enso Font.
|
||||
pub fn enso_font() -> NonVariableDefinition {
|
||||
ENSO_FONT_FAMILY_FONTS
|
||||
/// The Enso Font.
|
||||
pub fn font() -> NonVariableDefinition {
|
||||
FONTS
|
||||
.iter()
|
||||
.map(|(name, weight)| {
|
||||
let file = format!("Enso-{name}.ttf");
|
||||
@ -83,13 +84,30 @@ pub fn enso_font() -> NonVariableDefinition {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// All font faces contained in this font.
|
||||
pub fn faces() -> [NonVariableFaceHeader; 9] {
|
||||
[
|
||||
NonVariableFaceHeader { weight: ttf::Weight::Thin, ..default() },
|
||||
NonVariableFaceHeader { weight: ttf::Weight::ExtraLight, ..default() },
|
||||
NonVariableFaceHeader { weight: ttf::Weight::Light, ..default() },
|
||||
NonVariableFaceHeader { weight: ttf::Weight::Normal, ..default() },
|
||||
NonVariableFaceHeader { weight: ttf::Weight::Medium, ..default() },
|
||||
NonVariableFaceHeader { weight: ttf::Weight::SemiBold, ..default() },
|
||||
NonVariableFaceHeader { weight: ttf::Weight::Bold, ..default() },
|
||||
NonVariableFaceHeader { weight: ttf::Weight::ExtraBold, ..default() },
|
||||
NonVariableFaceHeader { weight: ttf::Weight::Black, ..default() },
|
||||
]
|
||||
}
|
||||
|
||||
/// Extract the fonts from the given archive file, and write them in the given directory.
|
||||
#[context("Failed to extract fonts from archive: {}", package.as_ref().display())]
|
||||
#[context("Failed to extract fonts from archive {}", package.as_ref().display())]
|
||||
pub async fn extract_fonts(
|
||||
fonts: &NonVariableDefinition,
|
||||
package: impl AsRef<Path>,
|
||||
out_dir: impl AsRef<Path>,
|
||||
) -> Result {
|
||||
use ide_ci::archive::extract_files::ExtractFiles;
|
||||
ide_ci::fs::tokio::create_dir_if_missing(out_dir.as_ref()).await?;
|
||||
let mut files_expected: HashSet<_> = fonts.files().collect();
|
||||
ide_ci::archive::tar::Archive::open_tar_gz(&package)
|
||||
.await?
|
||||
|
@ -119,9 +119,11 @@ impl CodeGenerator {
|
||||
// =================
|
||||
|
||||
pub async fn load_enso_font(out_dir: impl AsRef<Path>, code_gen: &mut CodeGenerator) -> Result {
|
||||
let family_name = enso_enso_font::ENSO_FONT_FAMILY_NAME;
|
||||
let font_family = enso_enso_font::enso_font();
|
||||
let package = enso_build::ide::web::fonts::get_enso_font_package().await?;
|
||||
let family_name = enso_enso_font::FONT_FAMILY;
|
||||
let font_family = enso_enso_font::font();
|
||||
let cache = ide_ci::cache::Cache::new_default().await?;
|
||||
let octocrab = ide_ci::github::setup_octocrab().await?;
|
||||
let package = enso_build::ide::web::enso_font::download(&cache, &octocrab).await?;
|
||||
enso_enso_font::extract_fonts(&font_family, package, &out_dir).await?;
|
||||
code_gen.add_non_variable_font_definition(family_name, &font_family);
|
||||
for file in font_family.files() {
|
||||
@ -143,8 +145,6 @@ mod google_fonts {
|
||||
use super::*;
|
||||
use crate::CodeGenerator;
|
||||
|
||||
use enso_build::ide::web::google_font::download_google_font;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FaceDefinition {
|
||||
file_name: String,
|
||||
@ -175,7 +175,9 @@ mod google_fonts {
|
||||
) -> Result<Vec<DownloadedFile>> {
|
||||
let octocrab = ide_ci::github::setup_octocrab().await?;
|
||||
let cache = ide_ci::cache::Cache::new_default().await?;
|
||||
let result = download_google_font(&cache, &octocrab, name.as_ref(), out_dir).await?;
|
||||
let result =
|
||||
enso_build::ide::web::google_font::install(&cache, &octocrab, name.as_ref(), out_dir)
|
||||
.await?;
|
||||
result
|
||||
.into_iter()
|
||||
.map(|font| Ok(DownloadedFile { name: font.try_file_name()?.as_str().into() }))
|
||||
|
Loading…
Reference in New Issue
Block a user