{
6 | if (panelModel?.metadata?.changed) {
7 | return panelModel.metadata.changed as any;
8 | }
9 |
10 | if (panelModel.sharedModel) {
11 | return panelModel.sharedModel.metadataChanged;
12 | }
13 |
14 | throw new Error('no metadata for panel');
15 | }
16 |
17 | export function getPanelMetadata(panelModel: INotebookModel, key: string): any {
18 | if (typeof panelModel.metadata.get === 'function') {
19 | return (panelModel as any).metadata.get(key);
20 | }
21 |
22 | if (panelModel.sharedModel) {
23 | return panelModel.sharedModel.getMetadata(key);
24 | }
25 |
26 | console.error('panel', panelModel);
27 | throw new Error('no metadata for panel');
28 | }
29 |
30 | export function setPanelMetadata(
31 | panelModel: INotebookModel,
32 | key: string,
33 | value: any,
34 | ): any {
35 | if (typeof panelModel.metadata.set === 'function') {
36 | return (panelModel as any).metadata.set(key, value);
37 | }
38 |
39 | if (panelModel.sharedModel) {
40 | return panelModel.sharedModel.setMetadata(key, value);
41 | }
42 |
43 | console.error('panel', panelModel);
44 | throw new Error('no metadata for panel');
45 | }
46 |
47 | export function getCellMetadata(cellModel: ICellModel, key: string): any {
48 | if (typeof cellModel.metadata.get === 'function') {
49 | return (cellModel as any).metadata.get(key);
50 | }
51 |
52 | if (cellModel.sharedModel) {
53 | return cellModel.sharedModel.getMetadata(key);
54 | }
55 |
56 | console.error('cell', cellModel);
57 | throw new Error('no metadata for cell');
58 | }
59 |
60 | export function setCellMetadata(cellModel: ICellModel, key: string, value: any): void {
61 | if (typeof cellModel.metadata.set === 'function') {
62 | return (cellModel as any).metadata.set(key, value);
63 | }
64 |
65 | if (cellModel.sharedModel) {
66 | return cellModel.sharedModel.setMetadata(key, value);
67 | }
68 |
69 | console.error('cell', cellModel);
70 | throw new Error('no metadata for cell');
71 | }
72 |
73 | export function deleteCellMetadata(cellModel: ICellModel, key: string): void {
74 | if (typeof cellModel.metadata.delete === 'function') {
75 | return (cellModel as any).metadata.delete(key);
76 | }
77 |
78 | if (cellModel.sharedModel) {
79 | return cellModel.sharedModel.deleteMetadata(key);
80 | }
81 |
82 | console.error('cell', cellModel);
83 | throw new Error('no metadata for cell');
84 | }
85 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | labels: bug
5 | ---
6 |
7 |
13 |
14 | ## Description
15 |
16 |
17 |
18 | ## Reproduce
19 |
20 |
21 |
22 | 1. Go to '...'
23 | 2. Click on '...'
24 | 3. Scroll down to '...'
25 | 4. See error '...'
26 |
27 |
29 |
30 | ## Expected behavior
31 |
32 |
33 |
34 | ## Context
35 |
36 |
37 |
38 | - Operating System and version:
39 | - Browser and version:
40 | - JupyterLab version:
41 | - jupyterlab-fonts version(s):
42 |
43 | Required: installed server extensions
44 |
45 | Paste the output from running `jupyter server extension list` (JupyterLab >= 3)
46 | or `jupyter serverextension list` (JupyterLab < 3) from the command line here.
47 | You may want to sanitize the paths in the output.
48 |
49 |
50 |
51 | Required: installed lab extensions
52 |
53 | Paste the output from running `jupyter labextension list` from the command line here.
54 | You may want to sanitize the paths in the output.
55 |
56 |
57 |
58 |
59 |
60 | Troubleshoot Output
61 |
62 | Paste the output from running `jupyter troubleshoot` from the command line here.
63 | You may want to sanitize the paths in the output.
64 |
65 |
66 |
67 | Command Line Output
68 |
69 | Paste the output from your command line running `jupyter lab` here, use `--debug` if possible.
70 |
71 |
72 |
73 | Browser Output (recommended for all interface issues)
74 |
75 | Paste the output from your browser JavaScript console replacing the text in here.
76 |
77 | To learn how to open the developer tools in your browser:
78 | https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools#How_to_open_the_devtools_in_your_browser
79 | If too many messages accumulated after many hours of working in JupyterLab, consider
80 | refreshing the window and then reproducing the bug to reduce the noise in the logs.
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/packages/jupyterlab-font-atkinson-hyperlegible/src/index.ts:
--------------------------------------------------------------------------------
1 | import { makePlugin } from '@deathbeds/jupyterlab-fonts';
2 |
3 | const regular = { fontStyle: 'normal', fontDisplay: 'swap', fontWeight: 400 };
4 | const italic = { fontStyle: 'italic' };
5 | const bold = { fontWeight: 700 };
6 |
7 | const plugin = makePlugin({
8 | id: '@deathbeds/jupyterlab-font-atkinson-hyperlegible',
9 | fontName: 'Atkinson Hyperlegible',
10 | license: {
11 | spdx: 'OFL-1.1',
12 | name: 'SIL Open Font License 1.1',
13 | holders: [`Copyright 2020 Braille Institute of America, Inc.`],
14 | },
15 | licenseText: async () => {
16 | return (await import('!!raw-loader!@fontsource/atkinson-hyperlegible/LICENSE'))
17 | .default;
18 | },
19 | variants: async () => {
20 | return {
21 | Regular: [
22 | {
23 | woff2: () =>
24 | import(
25 | `!!file-loader!@fontsource/atkinson-hyperlegible/files/atkinson-hyperlegible-latin-400-normal.woff2`
26 | ),
27 | style: { ...regular },
28 | },
29 | {
30 | woff2: () =>
31 | import(
32 | `!!file-loader!@fontsource/atkinson-hyperlegible/files/atkinson-hyperlegible-latin-ext-400-normal.woff2`
33 | ),
34 | style: { ...regular },
35 | },
36 | {
37 | woff2: () =>
38 | import(
39 | `!!file-loader!@fontsource/atkinson-hyperlegible/files/atkinson-hyperlegible-latin-400-italic.woff2`
40 | ),
41 | style: { ...regular, ...italic },
42 | },
43 | {
44 | woff2: () =>
45 | import(
46 | `!!file-loader!@fontsource/atkinson-hyperlegible/files/atkinson-hyperlegible-latin-ext-400-italic.woff2`
47 | ),
48 | style: { ...regular, ...italic },
49 | },
50 | ],
51 | Bold: [
52 | {
53 | woff2: () =>
54 | import(
55 | `!!file-loader!@fontsource/atkinson-hyperlegible/files/atkinson-hyperlegible-latin-700-normal.woff2`
56 | ),
57 | style: { ...regular, ...bold },
58 | },
59 | {
60 | woff2: () =>
61 | import(
62 | `!!file-loader!@fontsource/atkinson-hyperlegible/files/atkinson-hyperlegible-latin-700-italic.woff2`
63 | ),
64 | style: { ...regular, ...bold, ...italic },
65 | },
66 | {
67 | woff2: () =>
68 | import(
69 | `!!file-loader!@fontsource/atkinson-hyperlegible/files/atkinson-hyperlegible-latin-ext-700-normal.woff2`
70 | ),
71 | style: { ...regular, ...bold },
72 | },
73 | {
74 | woff2: () =>
75 | import(
76 | `!!file-loader!@fontsource/atkinson-hyperlegible/files/atkinson-hyperlegible-latin-ext-700-italic.woff2`
77 | ),
78 | style: { ...regular, ...bold, ...italic },
79 | },
80 | ],
81 | };
82 | },
83 | });
84 |
85 | export default plugin;
86 |
--------------------------------------------------------------------------------
/atest/Cells.robot:
--------------------------------------------------------------------------------
1 | *** Settings ***
2 | Documentation Cell-level styling works
3 |
4 | Library JupyterLibrary
5 | Resource ./_keywords.resource
6 |
7 | Suite Setup Set Attempt Screenshot Directory cells
8 | Suite Teardown Reset JupyterLab And Close With Coverage
9 | Test Teardown Clean Up After Cell Test
10 |
11 |
12 | *** Variables ***
13 | ${NS} @deathbeds/jupyterlab-fonts
14 | # lab4.0: using true empty {} fails to trigger `modelContentChanged`
15 | ${META_EMPTY} {"tags": [], "@deathbeds/jupyterlab-fonts": {"styles": {}}}
16 | ${META_RED} {"tags":["red"], "${NS}": {"styles": {"background-color": "red !important"}}}
17 | ${META_IMPORT} {"${NS}": {"styles": {"@import": "url('./style.css')"}}}
18 | ${RED_TAG} css:[${DATA_TAGS}=",red,"]
19 | ${ID_TAG} css:[${DATA_CELL_ID}]
20 |
21 |
22 | *** Test Cases ***
23 | Cell Styling (Lab)
24 | [Documentation] Can JupyterLab cells be styled?
25 | [Tags] app:lab
26 | Set Attempt Screenshot Directory cells${/}lab
27 | Launch A New JupyterLab Document
28 | Validate Notebook Cell Styles
29 |
30 | Cell Styling (Notebook)
31 | [Documentation] Can Jupyter Notebook cells be styled?
32 | [Tags] app:nb
33 | Set Attempt Screenshot Directory cells${/}nb
34 | Launch A New JupyterLab Document
35 | TRY
36 | ${old} = Open Notebook in Notebook Tab
37 | Execute JupyterLab Command Show Notebook Tools
38 | Validate Notebook Cell Styles
39 | FINALLY
40 | Capture Page Coverage
41 | Close Window
42 | Switch Window ${old}
43 | END
44 |
45 | Importing
46 | ${nbdir} = Get Jupyter Directory
47 | ${nburl} = Get Jupyter Server URL
48 | Create File ${nbdir}${/}style.css body { background-color: green; }
49 | Launch A New JupyterLab Document
50 | Wait Until JupyterLab Kernel Is Idle
51 | Set Cell Metadata ${META_IMPORT} 1 10-green.png
52 | Stylesheet Should Contain @import url('${nburl}files/./style.css')
53 | Capture Page Screenshot 11-end.png
54 |
55 |
56 | *** Keywords ***
57 | Clean Up After Cell Test
58 | Execute JupyterLab Command Close All Tabs
59 | ${nbdir} = Get Jupyter Directory
60 | Remove File ${nbdir}${/}Untitled.ipynb
61 |
62 | Validate Notebook Cell Styles
63 | Wait Until JupyterLab Kernel Is Idle
64 | Stylesheet Should Not Contain background-color: red
65 | Wait Until Page Contains Element ${ID_TAG}
66 | Wait Until Page Does Not Contain Element ${RED_TAG}
67 | Set Cell Metadata ${META_RED} 1 00-red.png
68 | Wait Until Page Contains Element ${RED_TAG}
69 | Stylesheet Should Contain background-color: red
70 | Set Cell Metadata ${META_EMPTY} 1 01-empty.png
71 | Wait Until Page Does Not Contain Element ${RED_TAG}
72 | Capture Page Screenshot 02-tag-cleaned.png
73 | Stylesheet Should Not Contain background-color: red
74 | Capture Page Screenshot 03-end.png
75 |
--------------------------------------------------------------------------------
/packages/jupyterlab-fonts/style/editor.css:
--------------------------------------------------------------------------------
1 | .jp-FontsEditor {
2 | color: var(--jp-ui-font-color0);
3 | overflow-y: auto;
4 | background-color: var(--jp-layout-color1);
5 | }
6 |
7 | .jp-FontsEditor-button.jp-mod-styled {
8 | font-size: var(--jp-ui-font-size1);
9 | background-color: var(--jp-layout-color0);
10 | border: solid 1px transparent;
11 | white-space: nowrap;
12 | flex: 0;
13 | }
14 |
15 | .jp-FontsEditor .lm-CommandPalette-header {
16 | margin: 0;
17 | }
18 |
19 | .jp-FontsEditor-enable label {
20 | margin: calc(var(--jp-ui-font-size1) / 2) 0 0 var(--jp-ui-font-size1);
21 | font-size: var(--jp-ui-font-size1);
22 | display: flex;
23 | flex-direction: row;
24 | align-items: center;
25 | }
26 |
27 | .jp-FontsEditor-button.jp-mod-styled:focus,
28 | .jp-FontsEditor-button.jp-mod-styled:hover {
29 | border-color: var(--jp-border-color1);
30 | }
31 |
32 | .jp-FontsEditor h2 {
33 | padding: var(--jp-ui-font-size1);
34 | margin: 0;
35 | display: flex;
36 | }
37 |
38 | .jp-FontsEditor h2 > label {
39 | flex: 1;
40 | }
41 |
42 | .jp-FontsEditor-delete-icon.jp-FontsEditor-button.jp-mod-styled {
43 | background-repeat: no-repeat;
44 | background-position: center;
45 | color: transparent;
46 | }
47 |
48 | .jp-FontsEditor h2 .jp-NotebookIcon {
49 | width: var(--jp-ui-font-size2);
50 | height: calc(var(--jp-ui-font-size2));
51 | display: inline-block;
52 | vertical-align: middle;
53 | background-repeat: no-repeat;
54 | }
55 |
56 | .jp-FontsEditor-embed {
57 | padding: 0;
58 | margin: 0;
59 | }
60 |
61 | .jp-FontsEditor-embed li {
62 | display: flex;
63 | flex-direction: row;
64 | list-style: none;
65 | align-items: baseline;
66 | justify-content: space-between;
67 | margin: 0 var(--jp-ui-font-size1);
68 | }
69 |
70 | .jp-FontsEditor-embed li > * {
71 | flex: 1;
72 | font-size: var(--jp-ui-font-size1);
73 | }
74 |
75 | .jp-FontsEditor-embed li > .jp-FontsEditor-size {
76 | flex: 0;
77 | padding: 0 var(--jp-ui-font-size1);
78 | font-size: var(--jp-ui-font-size0);
79 | color: var(--jp-ui-font-color2);
80 | white-space: nowrap;
81 | }
82 |
83 | .jp-FontsEditor-field {
84 | display: flex;
85 | margin: 0 var(--jp-ui-font-size1);
86 | align-items: baseline;
87 | }
88 |
89 | .jp-FontsEditor-field label {
90 | white-space: nowrap;
91 | flex: 0;
92 | font-size: var(--jp-ui-font-size1);
93 | padding-right: var(--jp-ui-font-size1);
94 | }
95 |
96 | .jp-FontsEditor-field > div {
97 | display: flex;
98 | flex: 1;
99 | flex-direction: row;
100 | align-items: baseline;
101 | }
102 | .jp-FontsEditor-field > div * {
103 | display: block;
104 | flex: 1;
105 | text-align: right;
106 | font-size: var(--jp-ui-font-size1);
107 | }
108 |
109 | .jp-FontsEditor-field select {
110 | font-weight: bold;
111 | border: none;
112 | background: transparent;
113 | background-image: var(--jp-ui-select-caret);
114 | background-repeat: no-repeat;
115 | background-position: 99% center;
116 | background-size: 18px;
117 | text-align: right;
118 | text-align-last: right;
119 | margin-left: var(--jp-notebook-padding);
120 | padding-right: calc(2 * var(--jp-notebook-padding));
121 | }
122 |
123 | .jp-FontsEditor option {
124 | text-align: right;
125 | }
126 |
--------------------------------------------------------------------------------
/packages/jupyterlab-font-fira-code/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@deathbeds/jupyterlab-font-fira-code",
3 | "version": "3.0.1",
4 | "description": "Fira Code Fonts for JupyterLab",
5 | "license": "BSD-3-Clause",
6 | "author": "Dead Pixels Collective",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/deathbeds/jupyterlab-fonts"
10 | },
11 | "bugs": {
12 | "url": "https://github.com/deathbeds/jupyterlab-fonts/issues"
13 | },
14 | "main": "lib/index.js",
15 | "files": [
16 | "{README.md,LICENSE}",
17 | "{lib,style,src}/*.{d.ts,js,css,ts,tsx,js.map}"
18 | ],
19 | "scripts": {
20 | "labextension:build": "jupyter labextension build .",
21 | "labextension:build:cov": "tsc -b tsconfig.cov.json && jupyter labextension build .",
22 | "watch": "jupyter labextension watch --debug ."
23 | },
24 | "types": "lib/index.d.ts",
25 | "dependencies": {
26 | "@deathbeds/jupyterlab-fonts": "~3.0.1",
27 | "@jupyterlab/application": "3 || 4",
28 | "firacode": "^6.2.0"
29 | },
30 | "devDependencies": {
31 | "@deathbeds/jupyterlab-fonts": "workspace:*",
32 | "@jupyterlab/builder": "^4.0.7"
33 | },
34 | "keywords": [
35 | "fonts",
36 | "jss",
37 | "jupyter",
38 | "jupyterlab",
39 | "jupyterlab-extension"
40 | ],
41 | "doitoml": {
42 | "prefix": "js-font-fira-code",
43 | "paths": {
44 | "npm_dist": [
45 | "../../dist/deathbeds-jupyterlab-font-fira-code-${JLF_VERSION}.tgz"
46 | ],
47 | "here": [
48 | "."
49 | ],
50 | "dist_pkg_json": [
51 | "../../src/_d/share/jupyter/labextensions/@deathbeds/jupyterlab-font-fira-code/package.json"
52 | ],
53 | "webpack_config": [
54 | "webpack.config.js"
55 | ]
56 | },
57 | "tasks": {
58 | "build": {
59 | "ext": {
60 | "actions": [
61 | [
62 | "::js-root::build_labext"
63 | ]
64 | ],
65 | "file_dep": [
66 | "::webpack_config",
67 | "package.json",
68 | "::js-root::tsbuildinfo",
69 | "::js-root::yarn_history"
70 | ],
71 | "targets": [
72 | "::dist_pkg_json"
73 | ]
74 | }
75 | },
76 | "dist": {
77 | "meta": {
78 | "doitoml": {
79 | "cwd": "../../dist"
80 | }
81 | },
82 | "file_dep": [
83 | "::js-root::tsbuildinfo",
84 | "LICENSE",
85 | "package.json",
86 | "README.md"
87 | ],
88 | "actions": [
89 | [
90 | "::dt::conda_run_build",
91 | "npm",
92 | "pack",
93 | "::here"
94 | ]
95 | ],
96 | "targets": [
97 | "::npm_dist"
98 | ]
99 | }
100 | }
101 | },
102 | "jupyterlab": {
103 | "extension": true,
104 | "outputDir": "../../src/_d/share/jupyter/labextensions/@deathbeds/jupyterlab-font-fira-code",
105 | "webpackConfig": "./webpack.config.js",
106 | "sharedPackages": {
107 | "@deathbeds/jupyterlab-fonts": {
108 | "bundled": false,
109 | "singleton": true
110 | }
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We would love to have contributions of:
4 |
5 | - additional fonts
6 | - additional strategies for making fonts available
7 |
8 | ## Development
9 |
10 | ### Before
11 |
12 | - Install [mambaforge](https://github.com/conda-forge/miniforge/releases/)
13 |
14 | ### Setup
15 |
16 | ```bash
17 | mamba create --file .github/locks/lock_linux-64.conda.lock --prefix ./.envs/lock
18 | source ./.envs/lock/bin/activate
19 | doit list
20 | ```
21 |
22 | ## Build Once
23 |
24 | ```bash
25 | doit dist
26 | ```
27 |
28 | ## Lab
29 |
30 | ```bash
31 | doit lab
32 | ```
33 |
34 | ### More Labs
35 |
36 | Create a `.env` file:
37 |
38 | ```ini
39 | # .env
40 | JLF_LAB=lab3.5 # default: lab4.0
41 | ```
42 |
43 | then
44 |
45 | ```bash
46 | doit lab
47 | ```
48 |
49 | ## Testing
50 |
51 | ```bash
52 | doit test
53 | ```
54 |
55 | ### Advanced testing
56 |
57 | #### JS Bundle analysis
58 |
59 | Create a `.env` file:
60 |
61 | ```ini
62 | WITH_JS_VIZ=1
63 | ```
64 |
65 | Then run:
66 |
67 | ```bash
68 | doit dist
69 | ```
70 |
71 | See `build/reports/webpack`.
72 |
73 | #### JS Coverage
74 |
75 | Create a `.env` file:
76 |
77 | ```ini
78 | WITH_JS_COV=1
79 | ```
80 |
81 | Run
82 |
83 | ```bash
84 | doit test
85 | # run some other excursions, by env var or `.env` file e.g.
86 | # JLF_LAB=lab3.5 doit test
87 | doit report
88 | ```
89 |
90 | See `build/reports/nyc/index.html`.
91 |
92 | ## Thinking about Committing
93 |
94 | ```bash
95 | doit fix lint dist test
96 | ```
97 |
98 | ## Updating environments
99 |
100 | - change the files in `.github/specs`
101 | - run `doit lock:solve:*`
102 | - commit `.github/locks`
103 |
104 | ## Releasing
105 |
106 | - make a release issue
107 | - ensure the CHANGELOG is updated
108 | - wait for CI
109 | - download the dist
110 | - make a GitHub release off `main`
111 | - upload the assets
112 | - upload to PyPI, npm
113 | - handle conda-forge chores
114 | - post-mortem
115 |
116 | ## Design Principles
117 |
118 | > Note: PRs will be reviewed on a time-permitting basis!
119 |
120 | ## Adding a Font
121 |
122 | While anyone can add more fonts as an extension, fonts included and maintained in this
123 | monorepo should be:
124 |
125 | - licensed and attributed for redistribution
126 | - available in `woff2` format
127 | - tested
128 |
129 | If there is an `npm` upstream for your font, great! However, if it ships every possible
130 | font format and weight, you should probably vendor it. For an example, see
131 | [jupyterlab-font-dejavu-sans-mono](./packages/jupyterlab-font-dejavu-sans-mono).
132 |
133 | ## Adding a Strategy
134 |
135 | For a number of reasons, this project is not going to go out of its way to support using
136 | fonts requiring your users to download fonts from the wild internet. However, if you
137 | were to build an extension for Google Fonts, TypeKit, or other sources of fonts, we
138 | would accept any PRs, pending review, that made this process more sane and secure.
139 |
140 | For the `nbconvert` preprocessor, other strategies, like external files, would be
141 | nice-to-have.
142 |
143 | ## Changing the Schema
144 |
145 | Aside from being difficult to develop, schema changes need to be addressed very
146 | gingerly: ideally, we would have a pattern for upgrading and downgrading font metadata
147 | on both the frontend and backend.
148 |
--------------------------------------------------------------------------------
/packages/jupyterlab-font-anonymous-pro/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@deathbeds/jupyterlab-font-anonymous-pro",
3 | "version": "3.0.1",
4 | "description": "Anonymous Pro Fonts for JupyterLab",
5 | "license": "BSD-3-Clause",
6 | "author": "Dead Pixels Collective",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/deathbeds/jupyterlab-fonts"
10 | },
11 | "bugs": {
12 | "url": "https://github.com/deathbeds/jupyterlab-fonts/issues"
13 | },
14 | "main": "lib/index.js",
15 | "files": [
16 | "vendor/anonymous-pro/LICENSE",
17 | "{README.md,LICENSE}",
18 | "{lib,src}/**/*.{d.ts,js,css,ts,tsx,js.map}"
19 | ],
20 | "scripts": {
21 | "labextension:build": "jupyter labextension build .",
22 | "labextension:build:cov": "tsc -b tsconfig.cov.json && jupyter labextension build .",
23 | "watch": "jupyter labextension watch --debug ."
24 | },
25 | "types": "lib/index.d.ts",
26 | "dependencies": {
27 | "@deathbeds/jupyterlab-fonts": "~3.0.1",
28 | "@jupyterlab/application": "3 || 4",
29 | "typeface-anonymous-pro": "^1.1.13"
30 | },
31 | "devDependencies": {
32 | "@deathbeds/jupyterlab-fonts": "workspace:*",
33 | "@jupyterlab/builder": "^4.0.7"
34 | },
35 | "keywords": [
36 | "fonts",
37 | "jss",
38 | "jupyter",
39 | "jupyterlab",
40 | "jupyterlab-extension"
41 | ],
42 | "doitoml": {
43 | "prefix": "js-font-anonymous-pro",
44 | "paths": {
45 | "npm_dist": [
46 | "../../dist/deathbeds-jupyterlab-font-anonymous-pro-${JLF_VERSION}.tgz"
47 | ],
48 | "dist_pkg_json": [
49 | "../../src/_d/share/jupyter/labextensions/@deathbeds/jupyterlab-font-anonymous-pro/package.json"
50 | ],
51 | "here": [
52 | "."
53 | ],
54 | "webpack_config": [
55 | "webpack.config.js"
56 | ]
57 | },
58 | "tasks": {
59 | "build": {
60 | "ext": {
61 | "actions": [
62 | [
63 | "::js-root::build_labext"
64 | ]
65 | ],
66 | "file_dep": [
67 | "package.json",
68 | "::webpack_config",
69 | "::js-root::tsbuildinfo",
70 | "::js-root::yarn_history"
71 | ],
72 | "targets": [
73 | "::dist_pkg_json"
74 | ]
75 | }
76 | },
77 | "dist": {
78 | "meta": {
79 | "doitoml": {
80 | "cwd": "../../dist"
81 | }
82 | },
83 | "file_dep": [
84 | "::js-root::tsbuildinfo",
85 | "LICENSE",
86 | "package.json",
87 | "README.md"
88 | ],
89 | "actions": [
90 | [
91 | "::dt::conda_run_build",
92 | "npm",
93 | "pack",
94 | "::here"
95 | ]
96 | ],
97 | "targets": [
98 | "::npm_dist"
99 | ]
100 | }
101 | }
102 | },
103 | "jupyterlab": {
104 | "extension": true,
105 | "outputDir": "../../src/_d/share/jupyter/labextensions/@deathbeds/jupyterlab-font-anonymous-pro",
106 | "webpackConfig": "./webpack.config.js",
107 | "sharedPackages": {
108 | "@deathbeds/jupyterlab-fonts": {
109 | "bundled": false,
110 | "singleton": true
111 | }
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/packages/jupyterlab-font-atkinson-hyperlegible/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@deathbeds/jupyterlab-font-atkinson-hyperlegible",
3 | "version": "3.0.1",
4 | "description": "Atkinson Hyperlegible for JupyterLab",
5 | "license": "BSD-3-Clause",
6 | "author": "Dead Pixels Collective",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/deathbeds/jupyterlab-fonts"
10 | },
11 | "bugs": {
12 | "url": "https://github.com/deathbeds/jupyterlab-fonts/issues"
13 | },
14 | "main": "lib/index.js",
15 | "files": [
16 | "{README.md,LICENSE}",
17 | "{lib,src}/*.{d.ts,js,css,ts,tsx,js.map}"
18 | ],
19 | "scripts": {
20 | "labextension:build": "jupyter labextension build .",
21 | "labextension:build:cov": "tsc -b tsconfig.cov.json && jupyter labextension build .",
22 | "watch": "jupyter labextension watch --debug ."
23 | },
24 | "types": "lib/index.d.ts",
25 | "dependencies": {
26 | "@deathbeds/jupyterlab-fonts": "~3.0.1",
27 | "@fontsource/atkinson-hyperlegible": "^5.0.15",
28 | "@jupyterlab/application": "3 || 4"
29 | },
30 | "devDependencies": {
31 | "@deathbeds/jupyterlab-fonts": "workspace:*",
32 | "@jupyterlab/builder": "^4.0.7"
33 | },
34 | "keywords": [
35 | "atkinson-hyperlegible",
36 | "fonts",
37 | "jss",
38 | "jupyter",
39 | "jupyterlab",
40 | "jupyterlab-extension"
41 | ],
42 | "doitoml": {
43 | "prefix": "js-font-atkinson-hyperlegible",
44 | "paths": {
45 | "npm_dist": [
46 | "../../dist/deathbeds-jupyterlab-font-atkinson-hyperlegible-${JLF_VERSION}.tgz"
47 | ],
48 | "here": [
49 | "."
50 | ],
51 | "dist_pkg_json": [
52 | "../../src/_d/share/jupyter/labextensions/@deathbeds/jupyterlab-font-atkinson-hyperlegible/package.json"
53 | ],
54 | "webpack_config": [
55 | "webpack.config.js"
56 | ]
57 | },
58 | "tasks": {
59 | "build": {
60 | "ext": {
61 | "actions": [
62 | [
63 | "::js-root::build_labext"
64 | ]
65 | ],
66 | "file_dep": [
67 | "::webpack_config",
68 | "package.json",
69 | "::js-root::tsbuildinfo",
70 | "::js-root::yarn_history"
71 | ],
72 | "targets": [
73 | "::dist_pkg_json"
74 | ]
75 | }
76 | },
77 | "dist": {
78 | "meta": {
79 | "doitoml": {
80 | "cwd": "../../dist"
81 | }
82 | },
83 | "file_dep": [
84 | "::js-root::tsbuildinfo",
85 | "LICENSE",
86 | "package.json",
87 | "README.md"
88 | ],
89 | "actions": [
90 | [
91 | "::dt::conda_run_build",
92 | "npm",
93 | "pack",
94 | "::here"
95 | ]
96 | ],
97 | "targets": [
98 | "::npm_dist"
99 | ]
100 | }
101 | }
102 | },
103 | "jupyterlab": {
104 | "extension": true,
105 | "outputDir": "../../src/_d/share/jupyter/labextensions/@deathbeds/jupyterlab-font-atkinson-hyperlegible",
106 | "webpackConfig": "./webpack.config.js",
107 | "sharedPackages": {
108 | "@deathbeds/jupyterlab-fonts": {
109 | "bundled": false,
110 | "singleton": true
111 | }
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es6: true,
5 | commonjs: true,
6 | node: true,
7 | },
8 | globals: { JSX: 'readonly' },
9 | root: true,
10 | extends: [
11 | 'eslint:recommended',
12 | 'plugin:import/errors',
13 | 'plugin:import/warnings',
14 | 'plugin:import/typescript',
15 | 'plugin:@typescript-eslint/eslint-recommended',
16 | 'plugin:@typescript-eslint/recommended',
17 | 'prettier',
18 | 'plugin:react/recommended',
19 | ],
20 | ignorePatterns: [
21 | '**/node_modules/**/*',
22 | '**/lib/**/*',
23 | '**/_*.ts',
24 | '**/_*.d.ts',
25 | '**/typings/**/*.d.ts',
26 | '**/dist/*',
27 | 'js/.eslintrc.js',
28 | ],
29 | parser: '@typescript-eslint/parser',
30 | parserOptions: {
31 | project: 'tsconfig.eslint.json',
32 | },
33 | plugins: ['@typescript-eslint', 'import'],
34 | rules: {
35 | '@typescript-eslint/camelcase': 'off',
36 | '@typescript-eslint/explicit-function-return-type': 'off',
37 | '@typescript-eslint/explicit-module-boundary-types': 'off',
38 | '@typescript-eslint/no-empty-interface': 'off',
39 | '@typescript-eslint/no-explicit-any': 'off',
40 | '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
41 | '@typescript-eslint/no-inferrable-types': 'off',
42 | '@typescript-eslint/no-namespace': 'off',
43 | '@typescript-eslint/no-non-null-assertion': 'off',
44 | '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }],
45 | '@typescript-eslint/no-use-before-define': 'off',
46 | '@typescript-eslint/no-var-requires': 'off',
47 | 'no-case-declarations': 'warn',
48 | 'no-control-regex': 'warn',
49 | 'no-inner-declarations': 'off',
50 | 'no-prototype-builtins': 'off',
51 | 'no-undef': 'warn',
52 | 'no-useless-escape': 'off',
53 | 'prefer-const': 'off',
54 | 'import/no-unresolved': 'off',
55 | 'import/order': [
56 | 'warn',
57 | {
58 | groups: [
59 | 'builtin',
60 | 'external',
61 | 'parent',
62 | 'sibling',
63 | 'index',
64 | 'object',
65 | 'unknown',
66 | ],
67 | pathGroups: [
68 | {
69 | pattern: 'react/**',
70 | group: 'builtin',
71 | position: 'after',
72 | },
73 | {
74 | pattern: 'codemirror/**',
75 | group: 'external',
76 | position: 'before',
77 | },
78 | {
79 | pattern: '@lumino/**',
80 | group: 'external',
81 | position: 'before',
82 | },
83 | {
84 | pattern: '@jupyterlab/**',
85 | group: 'external',
86 | position: 'after',
87 | },
88 | ],
89 | 'newlines-between': 'always',
90 | alphabetize: {
91 | order: 'asc',
92 | },
93 | },
94 | ],
95 | // deviations from jupyterlab, should probably be fixed
96 | 'import/no-cycle': 'off', // somehow we lapsed here... ~200 cycles now
97 | 'import/export': 'off', // we do class/interface + NS pun exports _all over_
98 | '@typescript-eslint/triple-slash-reference': 'off',
99 | 'no-async-promise-executor': 'off',
100 | 'prefer-spread': 'off',
101 | 'react/display-name': 'off',
102 | },
103 | settings: {
104 | react: {
105 | version: 'detect',
106 | },
107 | },
108 | };
109 |
--------------------------------------------------------------------------------
/packages/jupyterlab-fonts/src/plugin.ts:
--------------------------------------------------------------------------------
1 | import {
2 | JupyterFrontEndPlugin,
3 | JupyterFrontEnd,
4 | ILabShell,
5 | } from '@jupyterlab/application';
6 | import { ICommandPalette } from '@jupyterlab/apputils';
7 | import { IMainMenu } from '@jupyterlab/mainmenu';
8 | import { INotebookTracker } from '@jupyterlab/notebook';
9 | import { ISettingRegistry } from '@jupyterlab/settingregistry';
10 |
11 | import { NotebookFontsButton } from './button';
12 | import { FontEditor } from './editor';
13 | import { ICONS } from './icons';
14 | import { LicenseViewer } from './license';
15 | import { FontManager } from './manager';
16 | import { IFontManager, PACKAGE_NAME, CMD, IFontFaceOptions } from './tokens';
17 |
18 | import '../style/index.css';
19 |
20 | const PLUGIN_ID = `${PACKAGE_NAME}:fonts`;
21 |
22 | let licenseId = 0;
23 |
24 | const plugin: JupyterFrontEndPlugin = {
25 | id: PLUGIN_ID,
26 | autoStart: true,
27 | requires: [IMainMenu, ISettingRegistry, ICommandPalette, INotebookTracker],
28 | optional: [ILabShell],
29 | provides: IFontManager,
30 | activate: function (
31 | app: JupyterFrontEnd,
32 | menu: IMainMenu,
33 | settingRegistry: ISettingRegistry,
34 | palette: ICommandPalette,
35 | notebooks: INotebookTracker,
36 | labShell?: ILabShell | null,
37 | ): IFontManager {
38 | const manager = new FontManager(app.commands, palette, notebooks);
39 |
40 | const area = labShell ? 'main' : 'right';
41 |
42 | manager.licensePaneRequested.connect((it, font: IFontFaceOptions) => {
43 | let license = new LicenseViewer({ font });
44 | license.id = `jp-fonts-license-${licenseId++}`;
45 | license.title.label = font.name;
46 | license.title.closable = true;
47 | license.title.icon = ICONS.license;
48 | app.shell.add(license, area);
49 | app.shell.activateById(license.id);
50 | });
51 |
52 | menu.settingsMenu.addGroup([{ type: 'submenu', submenu: manager.menu }]);
53 |
54 | app.commands.addCommand(CMD.editFonts, {
55 | label: 'Global Fonts...',
56 | execute: (args) => {
57 | const editor = new FontEditor();
58 | const { model } = editor;
59 | model.fonts = manager;
60 | editor.title.icon = ICONS.fonts;
61 | editor.title.closable = true;
62 | if ((args || {})['global']) {
63 | editor.title.label = 'Global';
64 | editor.id = 'font-editor-global';
65 | } else {
66 | const { currentWidget } = notebooks;
67 | if (currentWidget == null) {
68 | return;
69 | }
70 | model.notebook = currentWidget;
71 | editor.id = `font-editor-${model.notebook.id}`;
72 | model.notebook.disposed.connect(() => editor.dispose());
73 | }
74 |
75 | app.shell.add(editor, area, { mode: 'split-right' });
76 | app.shell.activateById(editor.id);
77 | },
78 | });
79 |
80 | const fontsButton = new NotebookFontsButton();
81 | fontsButton.widgetRequested.connect(async () => {
82 | try {
83 | await app.commands.execute(CMD.editFonts);
84 | } catch (err) {
85 | console.warn(err);
86 | }
87 | });
88 |
89 | app.docRegistry.addWidgetExtension('Notebook', fontsButton);
90 |
91 | Promise.all([settingRegistry.load(PLUGIN_ID), app.restored])
92 | .then(async ([settings]) => {
93 | manager.settings = settings;
94 | settingRegistry
95 | .load('@jupyterlab/apputils-extension:themes')
96 | .then((settings) => {
97 | settings.changed.connect(() => {
98 | setTimeout(() => manager.hack(), 100);
99 | });
100 | })
101 | .catch(console.warn);
102 | })
103 | .catch((reason: Error) => {
104 | console.error(reason);
105 | });
106 |
107 | return manager;
108 | },
109 | };
110 |
111 | export default plugin;
112 |
--------------------------------------------------------------------------------
/packages/jupyterlab-font-dejavu-sans-mono/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@deathbeds/jupyterlab-font-dejavu-sans-mono",
3 | "version": "3.0.1",
4 | "description": "Dejavu Sans Mono Fonts for JupyterLab",
5 | "license": "BSD-3-Clause",
6 | "author": "Dead Pixels Collective",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/deathbeds/jupyterlab-fonts"
10 | },
11 | "bugs": {
12 | "url": "https://github.com/deathbeds/jupyterlab-fonts/issues"
13 | },
14 | "main": "lib/index.js",
15 | "files": [
16 | "vendor/dejavu-fonts-ttf/LICENSE",
17 | "{README.md,LICENSE}",
18 | "{lib,style,src}/**/*.{d.ts,js,css,woff2,ts,tsx,js.map}"
19 | ],
20 | "scripts": {
21 | "labextension:build": "jupyter labextension build .",
22 | "labextension:build:cov": "tsc -b tsconfig.cov.json && jupyter labextension build .",
23 | "prebuild": "python scripts/convert.py",
24 | "watch": "jupyter labextension watch --debug ."
25 | },
26 | "types": "lib/index.d.ts",
27 | "dependencies": {
28 | "@deathbeds/jupyterlab-fonts": "~3.0.1",
29 | "@jupyterlab/application": "3 || 4"
30 | },
31 | "devDependencies": {
32 | "@deathbeds/jupyterlab-fonts": "workspace:*",
33 | "@jupyterlab/builder": "^4.0.7",
34 | "dejavu-fonts-ttf": "^2.37.3"
35 | },
36 | "keywords": [
37 | "fonts",
38 | "jss",
39 | "jupyter",
40 | "jupyterlab",
41 | "jupyterlab-extension"
42 | ],
43 | "doitoml": {
44 | "prefix": "js-font-dejavu-sans-mono",
45 | "paths": {
46 | "npm_dist": [
47 | "../../dist/deathbeds-jupyterlab-font-dejavu-sans-mono-${JLF_VERSION}.tgz"
48 | ],
49 | "all_style": [
50 | "::prebuild_targets"
51 | ],
52 | "here": [
53 | "."
54 | ],
55 | "dist_pkg_json": [
56 | "../../src/_d/share/jupyter/labextensions/@deathbeds/jupyterlab-font-dejavu-sans-mono/package.json"
57 | ],
58 | "webpack_config": [
59 | "webpack.config.js"
60 | ],
61 | "prebuild_targets": [
62 | "style/fonts/DejaVuSansMono-Bold.woff2",
63 | "style/fonts/DejaVuSansMono.woff2"
64 | ],
65 | "prebuild_deps": [
66 | "scripts/convert.py"
67 | ]
68 | },
69 | "tasks": {
70 | "build": {
71 | "pre": {
72 | "actions": [
73 | [
74 | "::js-root::jlpm",
75 | "prebuild"
76 | ]
77 | ],
78 | "file_dep": [
79 | "::prebuild_deps",
80 | "::js-root::yarn_history"
81 | ],
82 | "targets": [
83 | "::prebuild_targets"
84 | ]
85 | },
86 | "ext": {
87 | "actions": [
88 | [
89 | "::js-root::build_labext"
90 | ]
91 | ],
92 | "file_dep": [
93 | "::webpack_config",
94 | "package.json",
95 | "::all_style",
96 | "::js-root::tsbuildinfo",
97 | "::js-root::yarn_history",
98 | "::prebuild_targets"
99 | ],
100 | "targets": [
101 | "::dist_pkg_json"
102 | ]
103 | }
104 | },
105 | "dist": {
106 | "meta": {
107 | "doitoml": {
108 | "cwd": "../../dist"
109 | }
110 | },
111 | "file_dep": [
112 | "::all_style",
113 | "::js-root::tsbuildinfo",
114 | "LICENSE",
115 | "package.json",
116 | "README.md"
117 | ],
118 | "actions": [
119 | [
120 | "::dt::conda_run_build",
121 | "npm",
122 | "pack",
123 | "::here"
124 | ]
125 | ],
126 | "targets": [
127 | "::npm_dist"
128 | ]
129 | }
130 | }
131 | },
132 | "jupyterlab": {
133 | "extension": true,
134 | "outputDir": "../../src/_d/share/jupyter/labextensions/@deathbeds/jupyterlab-font-dejavu-sans-mono",
135 | "webpackConfig": "./webpack.config.js",
136 | "sharedPackages": {
137 | "@deathbeds/jupyterlab-fonts": {
138 | "bundled": false,
139 | "singleton": true
140 | }
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/dodo.py:
--------------------------------------------------------------------------------
1 | """project automation for jupyterlab-fonts."""
2 | import json
3 | import os
4 | import platform
5 | import shutil
6 | import sys
7 | import warnings
8 | from pathlib import Path
9 |
10 | from doitoml import DoiTOML
11 | from yaml import safe_load
12 |
13 | DOT_ENV = Path(".env")
14 |
15 | dotenv_loaded = {}
16 |
17 | if DOT_ENV.exists():
18 | try:
19 | import dotenv
20 |
21 | dotenv_loaded = dotenv.dotenv_values(DOT_ENV)
22 | dotenv.load_dotenv()
23 | except ImportError as err:
24 | warnings.warn(
25 | f"{DOT_ENV} found, but cannot load python-dotenv: {err}",
26 | stacklevel=1,
27 | )
28 |
29 |
30 | class C:
31 |
32 | """Constants."""
33 |
34 | SUBDIRS = ["linux-64", "osx-64", "win-64"]
35 | THIS_SUBDIR = {"Linux": "linux-64", "Darwin": "osx-64", "Windows": "win-64"}[
36 | platform.system()
37 | ]
38 | THIS_PY = "{}.{}".format(*sys.version_info)
39 | PYTHONS = [
40 | "3.8",
41 | "3.11",
42 | ]
43 | PY_LABS = {
44 | "3.8": "lab3.5",
45 | "3.11": "lab4.0",
46 | }
47 | DEFAULT_PY = os.environ.get("JLF_PY", PYTHONS[-1])
48 | DEFAULT_LAB = PY_LABS[DEFAULT_PY]
49 | JLF_LAB = os.environ.get("JLF_LAB", DEFAULT_LAB)
50 | UTF8 = {"encoding": "utf-8"}
51 | JSON_FMT = {"indent": 2, "sort_keys": True}
52 | DEFAULT_SUBDIR = "linux-64"
53 | CI = bool(json.loads(os.environ.get("CI", "0")))
54 | RTD = os.environ.get("READTHEDOCS") == "True"
55 | DOCS_IN_CI = bool(json.loads(os.environ.get("DOCS_IN_CI", "0")))
56 | TEST_IN_CI = bool(json.loads(os.environ.get("TEST_IN_CI", "0")))
57 | DOCS_OR_TEST_IN_CI = DOCS_IN_CI or TEST_IN_CI
58 | DEMO_IN_BINDER = bool(json.loads(os.environ.get("DEMO_IN_BINDER", "0")))
59 | RUNNING_LOCALLY = not CI
60 | LAB_ARGS = safe_load(os.environ.get("LAB_ARGS", '["--no-browser", "--debug"]'))
61 |
62 | if CI:
63 | PY = Path(
64 | shutil.which("python3")
65 | or shutil.which("python")
66 | or shutil.which("python.exe"),
67 | ).resolve()
68 | CONDA_EXE = Path(
69 | shutil.which("conda")
70 | or shutil.which("conda.exe")
71 | or shutil.which("conda.cmd"),
72 | ).resolve()
73 | else:
74 | PY = "python.exe" if THIS_SUBDIR == "win-64" else "python"
75 | CONDA_EXE = "conda"
76 | PYM = [PY, "-m"]
77 |
78 |
79 | class P:
80 |
81 | """Paths."""
82 |
83 | DODO = Path(__file__)
84 | ROOT = DODO.parent
85 | BUILD = ROOT / "build"
86 | ENVS = ROOT / ".envs"
87 | DEV_PREFIX = ENVS / "dev"
88 |
89 | SYS_PREFIX = sys.prefix if C.CI or C.DEMO_IN_BINDER else str(DEV_PREFIX)
90 | SAFE_PATHS = [
91 | ROOT.as_posix(),
92 | *([str(SYS_PREFIX)] if C.CI or C.DEMO_IN_BINDER else []),
93 | ]
94 |
95 |
96 | env_patches = {
97 | "JLF_ROOT": P.ROOT,
98 | "CONDA_EXE": C.CONDA_EXE,
99 | "DEFAULT_LAB": C.DEFAULT_LAB,
100 | "JLF_LAB": C.JLF_LAB,
101 | "JLF_SYS_PREFIX": P.SYS_PREFIX,
102 | "THIS_PY": C.THIS_PY,
103 | "THIS_SUBDIR": C.THIS_SUBDIR,
104 | }
105 |
106 | if C.DEMO_IN_BINDER:
107 | env_patches["JLF_BUILD_PREFIX"] = P.SYS_PREFIX
108 | __import__("pprint").pprint(env_patches)
109 |
110 | # handle dynamic sys.prefix
111 | os.environ.update(
112 | {k: str(v) for k, v in env_patches.items()},
113 | )
114 |
115 | # configure doitoml, allowing changing `sys.prefix` in CI
116 | doitoml = DoiTOML(
117 | config_paths=[P.ROOT / "pyproject.toml"],
118 | safe_paths=P.SAFE_PATHS,
119 | )
120 | dt_dict = doitoml.config.to_dict()
121 | globals().update(doitoml.tasks())
122 |
123 |
124 | def _ok(name):
125 | print(f"OK {name}")
126 |
127 |
128 | def _phony(name, *extra):
129 | """Make a phony task."""
130 |
131 | def task():
132 | return {"actions": [(_ok, [name])], "task_dep": [f"*:{name}:*", *extra]}
133 |
134 | globals().update({f"task_{name}": task})
135 |
136 |
137 | _phony("fix")
138 | _phony("lint")
139 | _phony("dist")
140 | _phony("build", "*:build:*")
141 | _phony("test", "*:atest:*")
142 | _phony("lab", "dt:serve:lab")
143 | _phony("report")
144 | _phony("preflight", "*:preflight")
145 |
146 | if dotenv_loaded:
147 | os.environ.update(dotenv_loaded)
148 |
--------------------------------------------------------------------------------
/packages/jupyterlab-fonts/src/util.ts:
--------------------------------------------------------------------------------
1 | import { JupyterFrontEnd, JupyterFrontEndPlugin } from '@jupyterlab/application';
2 |
3 | import { IFontFacePrimitive } from './_schema';
4 | import {
5 | FONT_FORMATS,
6 | FontFormat,
7 | IFontManager,
8 | IMakeFaceOptions,
9 | IPluginOptions,
10 | } from './tokens';
11 |
12 | /* below from https://gist.github.com/viljamis/c4016ff88745a0846b94 */
13 | const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
14 |
15 | export function base64Encode(str: string): string {
16 | let out = '';
17 | let i = 0;
18 | let len = str.length;
19 | let c1: number;
20 | let c2: number;
21 | let c3: number;
22 |
23 | while (i < len) {
24 | c1 = str.charCodeAt(i++) & 0xff;
25 | if (i === len) {
26 | out += CHARS.charAt(c1 >> 2);
27 | out += CHARS.charAt((c1 & 0x3) << 4);
28 | out += '==';
29 | break;
30 | }
31 | c2 = str.charCodeAt(i++);
32 | if (i === len) {
33 | out += CHARS.charAt(c1 >> 2);
34 | out += CHARS.charAt(((c1 & 0x3) << 4) | ((c2 & 0xf0) >> 4));
35 | out += CHARS.charAt((c2 & 0xf) << 2);
36 | out += '=';
37 | break;
38 | }
39 | c3 = str.charCodeAt(i++);
40 | out += CHARS.charAt(c1 >> 2);
41 | out += CHARS.charAt(((c1 & 0x3) << 4) | ((c2 & 0xf0) >> 4));
42 | out += CHARS.charAt(((c2 & 0xf) << 2) | ((c3 & 0xc0) >> 6));
43 | out += CHARS.charAt(c3 & 0x3f);
44 | }
45 | return out;
46 | }
47 |
48 | export async function dataURISrc(url: string, format = FontFormat.woff2) {
49 | const pre = `data:${FONT_FORMATS[format]};charset=utf-8;base64`;
50 | const base64Font = base64Encode(await getBinary(url));
51 | const post = `format('${format}')`;
52 | const src = `url('${pre},${base64Font}') ${post}`;
53 | return src;
54 | }
55 |
56 | export function getBinary(url: string) {
57 | return new Promise((resolve, reject) => {
58 | const xhr = new XMLHttpRequest();
59 | xhr.open('GET', url, true);
60 | xhr.overrideMimeType('text/plain; charset=x-user-defined');
61 | xhr.onreadystatechange = () => {
62 | if (xhr.readyState === 4 && xhr.status === 200) {
63 | resolve(xhr.responseText);
64 | }
65 | };
66 | xhr.send();
67 | });
68 | }
69 |
70 | export async function makeFace(options: IMakeFaceOptions): Promise {
71 | return {
72 | ...options.primitive,
73 | 'font-family': `${options.name} ${options.variant}`,
74 | src: await dataURISrc((await options.woff2()).default, FontFormat.woff2),
75 | };
76 | }
77 |
78 | /**
79 | * Create an simple JupyterFrontEnd plugin for providing a font.
80 | *
81 | * This will lazily load its font assets when needed.
82 | */
83 | export function makePlugin(options: IPluginOptions): JupyterFrontEndPlugin {
84 | const plugin: JupyterFrontEndPlugin = {
85 | id: options.id,
86 | autoStart: true,
87 | requires: [IFontManager],
88 | activate: (app: JupyterFrontEnd, fonts: IFontManager): void => {
89 | fonts.ready
90 | .then(async () => {
91 | const variants = await options.variants();
92 | for (const [variant, faces] of Object.entries(variants)) {
93 | fonts.registerFontFace({
94 | name: `${options.fontName} ${variant}`.trim(),
95 | license: {
96 | ...options.license,
97 | text: async () => await options.licenseText(),
98 | },
99 | faces: async () => {
100 | const promises: Promise[] = [];
101 | for (const face of faces) {
102 | promises.push(
103 | makeFace({
104 | name: options.fontName,
105 | variant,
106 | woff2: face.woff2,
107 | primitive: face.style || {},
108 | }),
109 | );
110 | }
111 | try {
112 | return await Promise.all(promises);
113 | } catch (err) {
114 | console.warn(err);
115 | return [];
116 | }
117 | },
118 | });
119 | }
120 | })
121 | .catch(console.warn);
122 | },
123 | };
124 | return plugin;
125 | }
126 |
--------------------------------------------------------------------------------
/packages/jupyterlab-font-anonymous-pro/vendor/anonymous-pro/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009, Mark Simonson (http://www.ms-studio.com, mark@marksimonson.com),
2 | with Reserved Font Name Anonymous Pro Minus.
3 |
4 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
5 | This license is copied below, and is also available with a FAQ at:
6 | http://scripts.sil.org/OFL
7 |
8 |
9 | -----------------------------------------------------------
10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
11 | -----------------------------------------------------------
12 |
13 | PREAMBLE
14 | The goals of the Open Font License (OFL) are to stimulate worldwide
15 | development of collaborative font projects, to support the font creation
16 | efforts of academic and linguistic communities, and to provide a free and
17 | open framework in which fonts may be shared and improved in partnership
18 | with others.
19 |
20 | The OFL allows the licensed fonts to be used, studied, modified and
21 | redistributed freely as long as they are not sold by themselves. The
22 | fonts, including any derivative works, can be bundled, embedded,
23 | redistributed and/or sold with any software provided that any reserved
24 | names are not used by derivative works. The fonts and derivatives,
25 | however, cannot be released under any other type of license. The
26 | requirement for fonts to remain under this license does not apply
27 | to any document created using the fonts or their derivatives.
28 |
29 | DEFINITIONS
30 | "Font Software" refers to the set of files released by the Copyright
31 | Holder(s) under this license and clearly marked as such. This may
32 | include source files, build scripts and documentation.
33 |
34 | "Reserved Font Name" refers to any names specified as such after the
35 | copyright statement(s).
36 |
37 | "Original Version" refers to the collection of Font Software components as
38 | distributed by the Copyright Holder(s).
39 |
40 | "Modified Version" refers to any derivative made by adding to, deleting,
41 | or substituting -- in part or in whole -- any of the components of the
42 | Original Version, by changing formats or by porting the Font Software to a
43 | new environment.
44 |
45 | "Author" refers to any designer, engineer, programmer, technical
46 | writer or other person who contributed to the Font Software.
47 |
48 | PERMISSION & CONDITIONS
49 | Permission is hereby granted, free of charge, to any person obtaining
50 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
51 | redistribute, and sell modified and unmodified copies of the Font
52 | Software, subject to the following conditions:
53 |
54 | 1) Neither the Font Software nor any of its individual components,
55 | in Original or Modified Versions, may be sold by itself.
56 |
57 | 2) Original or Modified Versions of the Font Software may be bundled,
58 | redistributed and/or sold with any software, provided that each copy
59 | contains the above copyright notice and this license. These can be
60 | included either as stand-alone text files, human-readable headers or
61 | in the appropriate machine-readable metadata fields within text or
62 | binary files as long as those fields can be easily viewed by the user.
63 |
64 | 3) No Modified Version of the Font Software may use the Reserved Font
65 | Name(s) unless explicit written permission is granted by the corresponding
66 | Copyright Holder. This restriction only applies to the primary font name as
67 | presented to the users.
68 |
69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
70 | Software shall not be used to promote, endorse or advertise any
71 | Modified Version, except to acknowledge the contribution(s) of the
72 | Copyright Holder(s) and the Author(s) or with their explicit written
73 | permission.
74 |
75 | 5) The Font Software, modified or unmodified, in part or in whole,
76 | must be distributed entirely under this license, and must not be
77 | distributed under any other license. The requirement for fonts to
78 | remain under this license does not apply to any document created
79 | using the Font Software.
80 |
81 | TERMINATION
82 | This license becomes null and void if any of the above conditions are
83 | not met.
84 |
85 | DISCLAIMER
86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
94 | OTHER DEALINGS IN THE FONT SOFTWARE.
95 |
--------------------------------------------------------------------------------
/packages/jupyterlab-fonts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@deathbeds/jupyterlab-fonts",
3 | "version": "3.0.1",
4 | "description": "Interactive Typography and Style for JupyterLab",
5 | "license": "BSD-3-Clause",
6 | "author": "Dead Pixels Collective",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/deathbeds/jupyterlab-fonts"
10 | },
11 | "bugs": {
12 | "url": "https://github.com/deathbeds/jupyterlab-fonts/issues"
13 | },
14 | "main": "lib/index.js",
15 | "files": [
16 | "{LICENSE,README.md}",
17 | "{lib,style,schema,src}/**/*.{d.ts,js,css,svg,json,ts,tsx,js.map}"
18 | ],
19 | "scripts": {
20 | "labextension:build": "jupyter labextension build .",
21 | "labextension:build:cov": "tsc -b tsconfig.cov.json && jupyter labextension build .",
22 | "prebuild": "jlpm prebuild:prep && jlpm prebuild:schema && jlpm prebuild:copy",
23 | "prebuild:copy": "jlpm prettier src/_schema.d.ts > lib/_schema.d.ts",
24 | "prebuild:prep": "mkdirp lib",
25 | "prebuild:schema": "json2ts schema/fonts.json --strictIndexSignatures | prettier --stdin-filepath _schema.d.ts > src/_schema.d.ts",
26 | "watch": "jupyter labextension watch --debug ."
27 | },
28 | "types": "lib/index.d.ts",
29 | "dependencies": {
30 | "@jupyterlab/application": "3 || 4",
31 | "@jupyterlab/mainmenu": "3 || 4",
32 | "@jupyterlab/notebook": "3 || 4",
33 | "jss": "^10.10.0",
34 | "jss-preset-default": "^10.10.0"
35 | },
36 | "devDependencies": {
37 | "@jupyterlab/builder": "^4.0.7",
38 | "json-schema-to-typescript": "^11.0.2",
39 | "mkdirp": "^3.0.1",
40 | "prettier": "^3.0.2"
41 | },
42 | "keywords": [
43 | "fonts",
44 | "jss",
45 | "jupyter",
46 | "jupyterlab",
47 | "jupyterlab-extension"
48 | ],
49 | "doitoml": {
50 | "prefix": "js-fonts",
51 | "env": {
52 | "JLF_VERSION": ":get::json::package.json::version",
53 | "JLF_NAME": ":get::json::package.json::name"
54 | },
55 | "paths": {
56 | "npm_dist": [
57 | "../../dist/deathbeds-jupyterlab-fonts-${JLF_VERSION}.tgz"
58 | ],
59 | "all_style": [
60 | ":rglob::style::*.*"
61 | ],
62 | "all_schema": [
63 | ":glob::schema::*.json"
64 | ],
65 | "all_ts": [
66 | ":rglob::src::*.ts",
67 | ":rglob::src::*.tsx"
68 | ],
69 | "here": [
70 | "."
71 | ],
72 | "dist_pkg_json": [
73 | "../../src/_d/share/jupyter/labextensions/@deathbeds/jupyterlab-fonts/package.json"
74 | ],
75 | "prebuild_deps": [
76 | "schema/fonts.json"
77 | ],
78 | "prebuild_targets": [
79 | "src/_schema.d.ts",
80 | "lib/_schema.d.ts"
81 | ],
82 | "webpack_config": [
83 | "webpack.config.js"
84 | ]
85 | },
86 | "tasks": {
87 | "build": {
88 | "pre": {
89 | "actions": [
90 | [
91 | "::js-root::jlpm",
92 | "prebuild"
93 | ]
94 | ],
95 | "file_dep": [
96 | "::prebuild_deps",
97 | "::js-root::yarn_history"
98 | ],
99 | "targets": [
100 | "::prebuild_targets"
101 | ]
102 | },
103 | "ext": {
104 | "actions": [
105 | [
106 | "::js-root::build_labext"
107 | ]
108 | ],
109 | "file_dep": [
110 | "::webpack_config",
111 | "package.json",
112 | "::all_style",
113 | "::all_ts",
114 | "::all_schema",
115 | "::js-root::tsbuildinfo",
116 | "::js-root::yarn_history"
117 | ],
118 | "targets": [
119 | "::dist_pkg_json"
120 | ]
121 | }
122 | },
123 | "dist": {
124 | "meta": {
125 | "doitoml": {
126 | "cwd": "../../dist"
127 | }
128 | },
129 | "file_dep": [
130 | "::all_style",
131 | "::js-root::tsbuildinfo",
132 | "LICENSE",
133 | "package.json",
134 | "README.md"
135 | ],
136 | "actions": [
137 | [
138 | "::dt::conda_run_build",
139 | "npm",
140 | "pack",
141 | "::here"
142 | ]
143 | ],
144 | "targets": [
145 | "::npm_dist"
146 | ]
147 | }
148 | }
149 | },
150 | "jupyterlab": {
151 | "extension": "lib/plugin.js",
152 | "schemaDir": "schema",
153 | "outputDir": "../../src/_d/share/jupyter/labextensions/@deathbeds/jupyterlab-fonts",
154 | "webpackConfig": "./webpack.config.js",
155 | "sharedPackages": {
156 | "jss": {
157 | "bundled": true
158 | },
159 | "jss-preset-default": {
160 | "bundled": true
161 | }
162 | }
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/.github/locks/atest_osx-64.conda.lock:
--------------------------------------------------------------------------------
1 | # channels:
2 | # - conda-forge
3 | # - nodefaults
4 | # dependencies:
5 | # - firefox >=115,<116
6 | # - geckodriver
7 | # - pip
8 | # - robotframework >=6.1
9 | # - robotframework-jupyterlibrary >=0.5.0
10 | # - robotframework-pabot
11 |
12 | @EXPLICIT
13 | https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h10d778d_5.conda#6097a6ca9ada32699b5fc4312dd6ef18
14 | https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2023.11.17-h8857fd0_0.conda#c687e9d14c49e3d3946d50a413cdbf16
15 | https://conda.anaconda.org/conda-forge/osx-64/libcxx-16.0.6-hd57cbcb_0.conda#7d6972792161077908b62971802f289a
16 | https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.5.0-hf0c8a7f_1.conda#6c81cb022780ee33435cca0127dd43c9
17 | https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2#ccb34fb14960ad8b125962d3d79b31a9
18 | https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.2.13-h8a1eda9_5.conda#4a3ad23f6e16f99c04e166767193d700
19 | https://conda.anaconda.org/conda-forge/osx-64/python_abi-3.12-4_cp312.conda#87201ac4314b911b74197e588cca3639
20 | https://conda.anaconda.org/conda-forge/osx-64/selenium-manager-4.16.0-h63b85fc_0.conda#2c03a69d0d192b30f0fa4a31ebb4a13d
21 | https://conda.anaconda.org/conda-forge/noarch/tzdata-2023c-h71feb2d_0.conda#939e3e74d8be4dac89ce83b20de2492a
22 | https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2#a72f9d4ea13d55d745ff1ed594747f10
23 | https://conda.anaconda.org/conda-forge/osx-64/firefox-115.5.0esr-h93d8f39_0.conda#ed139a8e65c087ce247ea625db19be98
24 | https://conda.anaconda.org/conda-forge/osx-64/geckodriver-0.33.0-h08401f6_1.conda#42b774349e67afb2616e1de845a9af38
25 | https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.44.2-h92b6c6a_0.conda#d4419f90019e6a2b152cd4d32f73a82f
26 | https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.4-h93d8f39_2.conda#e58f366bd4d767e9ab97ab8b272e7670
27 | https://conda.anaconda.org/conda-forge/osx-64/openssl-3.2.0-hd75f5a5_1.conda#06cb561619487c88891839b9beb5244c
28 | https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda#bf830ba5afc507c6232d4ef0fb1a882d
29 | https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda#f17f77f2acf4d344734bda76829ce14e
30 | https://conda.anaconda.org/conda-forge/osx-64/python-3.12.0-h30d4d87_0_cpython.conda#d11dc8f4551011fb6baa2865f1ead48f
31 | https://conda.anaconda.org/conda-forge/noarch/attrs-23.1.0-pyh71513ae_1.conda#3edfead7cedd1ab4400a6c588f3e75f8
32 | https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py312heafc425_1.conda#a288b88f06b8bfe0dedaf5c4b6ac6b7a
33 | https://conda.anaconda.org/conda-forge/noarch/certifi-2023.11.17-pyhd8ed1ab_0.conda#2011bcf45376341dd1d690263fdbc789
34 | https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.0-pyhd8ed1ab_0.conda#f6c211fee3c98229652b60a9a42ef363
35 | https://conda.anaconda.org/conda-forge/noarch/idna-3.6-pyhd8ed1ab_0.conda#1a76f09108576397c41c0b0c5bd84134
36 | https://conda.anaconda.org/conda-forge/noarch/natsort-8.4.0-pyhd8ed1ab_0.conda#70959cd1db3cf77b2a27a0836cfd08a7
37 | https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025
38 | https://conda.anaconda.org/conda-forge/noarch/robotframework-6.1.1-pyhd8ed1ab_1.conda#c7719c3e8bcabd3b30d989096a9268b3
39 | https://conda.anaconda.org/conda-forge/noarch/setuptools-68.2.2-pyhd8ed1ab_0.conda#fc2166155db840c634a1291a5c35a709
40 | https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.0-pyhd8ed1ab_0.tar.bz2#dd6cbc539e74cb1f430efbd4575b9303
41 | https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2#6d6552722448103793743dabfbda532d
42 | https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.9.0-pyha770c72_0.conda#a92a6440c3fe7052d63244f3aba2a4a7
43 | https://conda.anaconda.org/conda-forge/noarch/wheel-0.42.0-pyhd8ed1ab_0.conda#1cdea58981c5cbc17b51973bcaddcea7
44 | https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2#b21ed0883505ba1910994f1df031a428
45 | https://conda.anaconda.org/conda-forge/noarch/outcome-1.3.0.post0-pyhd8ed1ab_0.conda#c2954fba1935b5775755e3f14d951af0
46 | https://conda.anaconda.org/conda-forge/noarch/pip-23.3.1-pyhd8ed1ab_0.conda#2400c0b86889f43aa52067161e1fb108
47 | https://conda.anaconda.org/conda-forge/noarch/robotframework-pythonlibcore-4.3.0-pyhd8ed1ab_0.conda#ee96d096541f6a89fb6d2a1ae21587ee
48 | https://conda.anaconda.org/conda-forge/noarch/robotframework-stacktrace-0.4.1-pyhd8ed1ab_0.tar.bz2#3dc788e294fd159537c931dbb964511e
49 | https://conda.anaconda.org/conda-forge/noarch/urllib3-2.1.0-pyhd8ed1ab_0.conda#f8ced8ee63830dec7ecc1be048d1470a
50 | https://conda.anaconda.org/conda-forge/noarch/robotframework-pabot-2.16.0-pyhd8ed1ab_0.conda#d5cef1ba9df784f3d4633134a5e23b4e
51 | https://conda.anaconda.org/conda-forge/osx-64/trio-0.23.1-py312hb401068_1.conda#4af127fa9ee99b9ec84fc5441c512bdf
52 | https://conda.anaconda.org/conda-forge/noarch/wsproto-1.2.0-pyhd8ed1ab_0.tar.bz2#00ba804b54f451d102f6a7615f08470d
53 | https://conda.anaconda.org/conda-forge/noarch/trio-websocket-0.11.1-pyhd8ed1ab_0.conda#020557a424faf98154938da4426fe987
54 | https://conda.anaconda.org/conda-forge/noarch/selenium-4.16.0-pyhd8ed1ab_0.conda#bdfe352529493afd8db9103df11e0bbf
55 | https://conda.anaconda.org/conda-forge/noarch/robotframework-seleniumlibrary-6.2.0-pyhd8ed1ab_0.conda#bef7db30c8d6d75e29318179f8fcee64
56 | https://conda.anaconda.org/conda-forge/noarch/robotframework-jupyterlibrary-0.5.0-pyhd8ed1ab_0.conda#f7b1b4f48c78c3feb041d7fb45d68910
57 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: ["*"]
8 | workflow_dispatch:
9 |
10 | concurrency:
11 | group: ${{ github.ref }}
12 | cancel-in-progress: true
13 |
14 | env:
15 | PIP_DISABLE_PIP_VERSION_CHECK: 1
16 | PYTHONIOENCODING: utf-8
17 | PYTHONUNBUFFERED: 1
18 | MAMBA_NO_BANNER: 1
19 | # ours
20 | CACHE_EPOCH: 3
21 | ATEST_RETRIES: 3
22 |
23 | defaults:
24 | run:
25 | shell: bash -l {0}
26 |
27 | jobs:
28 | build:
29 | runs-on: ubuntu-latest
30 | steps:
31 | - name: checkout
32 | uses: actions/checkout@v4
33 |
34 | - name: install (conda)
35 | uses: conda-incubator/setup-miniconda@v3
36 | with:
37 | miniforge-variant: Mambaforge
38 | environment-file: .github/locks/lock_linux-64.conda.lock
39 | use-mamba: true
40 |
41 | - name: preflight
42 | run: doit preflight
43 |
44 | - name: setup (python)
45 | run: doit lock:install:build_linux-64_lab4.0
46 |
47 | - name: cache (node)
48 | uses: actions/cache@v3
49 | id: cache-node-modules
50 | with:
51 | path: |-
52 | **/node_modules
53 | build/.cache/yarn
54 | key: |
55 | ${{ env.CACHE_EPOCH }}-ubuntu-node-modules-${{ hashFiles('yarn.lock') }}
56 |
57 | - name: setup (js)
58 | run: doit js-root:setup
59 |
60 | - name: build
61 | run: doit build || doit list
62 |
63 | - name: build (retry)
64 | run: doit build
65 |
66 | - name: dist
67 | run: doit dist
68 |
69 | - name: upload (dist)
70 | uses: actions/upload-artifact@v3
71 | with:
72 | name: jupyterlab-fonts-${{ github.run_number }}-dist
73 | path: ./dist
74 |
75 | - name: upload (preflight)
76 | uses: actions/upload-artifact@v3
77 | with:
78 | name: jupyterlab-fonts-${{ github.run_number }}-lock-preflight
79 | path: ./build/locks
80 |
81 | - name: Rename uncached conda packages
82 | run: mv "${CONDA_PKGS_DIR}" "${CONDA_PKGS_DIR}_do_not_cache"
83 |
84 |
85 | lint:
86 | runs-on: ubuntu-latest
87 | steps:
88 | - name: checkout
89 | uses: actions/checkout@v4
90 |
91 | - name: install (conda)
92 | uses: conda-incubator/setup-miniconda@v3
93 | with:
94 | miniforge-variant: Mambaforge
95 | environment-file: .github/locks/lock_linux-64.conda.lock
96 | use-mamba: true
97 |
98 | - name: preflight
99 | run: doit lock:preflight
100 |
101 | - name: setup (python)
102 | run: doit lock:install:build_linux-64_lab4.0
103 |
104 | - name: cache (node)
105 | uses: actions/cache@v3
106 | id: cache-node-modules
107 | with:
108 | path: |-
109 | **/node_modules
110 | build/.cache/yarn
111 | key: |
112 | ${{ env.CACHE_EPOCH }}-ubuntu-node-modules-${{ hashFiles('yarn.lock') }}
113 |
114 | - name: setup (js)
115 | run: doit js-root:setup
116 |
117 | - name: lint
118 | run: doit lint
119 |
120 | - name: Rename uncached conda packages
121 | run: mv "${CONDA_PKGS_DIR}" "${CONDA_PKGS_DIR}_do_not_cache"
122 |
123 | test:
124 | needs: [build, lint]
125 | name: test ${{ matrix.os }} ${{ matrix.lab-version }}
126 | runs-on: ${{ matrix.os }}-latest
127 | env:
128 | TESTING_IN_CI: '1'
129 | JLF_LAB: ${{ matrix.lab-version }}
130 |
131 | strategy:
132 | fail-fast: false
133 | matrix:
134 | os: ['ubuntu', 'macos', 'windows']
135 | lab-version: ['lab3.5', 'lab4.0']
136 | include:
137 | - os: ubuntu
138 | subdir: linux-64
139 | - os: macos
140 | subdir: osx-64
141 | - os: windows
142 | subdir: win-64
143 |
144 | steps:
145 | - name: configure line endings
146 | run: git config --global core.autocrlf false
147 |
148 | - name: checkout
149 | uses: actions/checkout@v4
150 |
151 | - name: install (conda)
152 | uses: conda-incubator/setup-miniconda@v3
153 | with:
154 | miniforge-variant: Mambaforge
155 | environment-file: .github/locks/lock_${{ matrix.subdir }}.conda.lock
156 | use-mamba: true
157 |
158 | - name: download (preflight)
159 | uses: actions/download-artifact@v3
160 | with:
161 | name: jupyterlab-fonts-${{ github.run_number }}-lock-preflight
162 | path: ./build/locks
163 |
164 | - name: download (dist)
165 | uses: actions/download-artifact@v3
166 | with:
167 | name: jupyterlab-fonts-${{ github.run_number }}-dist
168 | path: ./dist
169 |
170 | - name: test (pytest)
171 | run: doit dt:test:pytest || doit list
172 |
173 | - name: test (pytest, retry)
174 | run: doit dt:test:pytest
175 |
176 | - name: test (robot)
177 | run: doit dt:atest:a_1
178 |
179 | - name: test (robot, retry 1)
180 | run: doit dt:atest:a_2
181 |
182 | - name: test (robot, retry 2)
183 | run: doit dt:atest:a_3
184 |
185 | - name: upload (reports)
186 | if: always()
187 | uses: actions/upload-artifact@v3
188 | with:
189 | name: |
190 | jupyterlab-fonts-${{ github.run_number }}-reports-${{ runner.os }}-f${{ matrix.lab-version }}
191 | path: ./build/reports
192 |
193 | - name: Rename uncached conda packages
194 | run: mv "${CONDA_PKGS_DIR}" "${CONDA_PKGS_DIR}_do_not_cache"
195 |
--------------------------------------------------------------------------------
/.github/locks/atest_win-64.conda.lock:
--------------------------------------------------------------------------------
1 | # channels:
2 | # - conda-forge
3 | # - nodefaults
4 | # dependencies:
5 | # - firefox >=115,<116
6 | # - geckodriver
7 | # - pip
8 | # - robotframework >=6.1
9 | # - robotframework-jupyterlibrary >=0.5.0
10 | # - robotframework-pabot
11 |
12 | @EXPLICIT
13 | https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2023.11.17-h56e8100_0.conda#1163114b483f26761f993c709e65271f
14 | https://conda.anaconda.org/conda-forge/win-64/libexpat-2.5.0-h63175ca_1.conda#636cc3cbbd2e28bcfd2f73b2044aac2c
15 | https://conda.anaconda.org/conda-forge/win-64/python_abi-3.12-4_cp312.conda#17f4ccf6be9ded08bd0a376f489ac1a6
16 | https://conda.anaconda.org/conda-forge/noarch/tzdata-2023c-h71feb2d_0.conda#939e3e74d8be4dac89ce83b20de2492a
17 | https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_0.tar.bz2#72608f6cd3e5898229c3ea16deb1ac43
18 | https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.36.32532-hdcecf7f_17.conda#d0de20f2f3fc806a81b44fcdd941aaf7
19 | https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h64f974e_17.conda#67ff6791f235bb606659bf2a5c169191
20 | https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.36.32532-h05e6639_17.conda#4618046c39f7c81861e53ded842e738a
21 | https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-hcfcfb64_5.conda#26eb8ca6ea332b675e11704cce84a3be
22 | https://conda.anaconda.org/conda-forge/win-64/firefox-115.5.0esr-h63175ca_0.conda#25240c50795a28d24d3cc211fcda47df
23 | https://conda.anaconda.org/conda-forge/win-64/geckodriver-0.33.0-h611cf2b_1.conda#c37f0f580da564b1108cff23b6d55b34
24 | https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.2-h8ffe710_5.tar.bz2#2c96d1b6915b408893f9472569dee135
25 | https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.44.2-hcfcfb64_0.conda#4a5f5ab56cbf3ccd08d71a1168061213
26 | https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.13-hcfcfb64_5.conda#5fdb9c6a113b6b6cb5e517fd972d5f41
27 | https://conda.anaconda.org/conda-forge/win-64/openssl-3.2.0-hcfcfb64_1.conda#d10167022f99bad12ee07dea022d5830
28 | https://conda.anaconda.org/conda-forge/win-64/selenium-manager-4.16.0-h975169c_0.conda#f828d7757deea67fa35da166459c6212
29 | https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h5226925_1.conda#fc048363eb8f03cd1737600a5d08aafe
30 | https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2#515d77642eaa3639413c6b1bc3f94219
31 | https://conda.anaconda.org/conda-forge/win-64/python-3.12.0-h2628c8c_0_cpython.conda#defd5d375853a2caff36a19d2d81a28e
32 | https://conda.anaconda.org/conda-forge/noarch/attrs-23.1.0-pyh71513ae_1.conda#3edfead7cedd1ab4400a6c588f3e75f8
33 | https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.1.0-py312h53d5487_1.conda#d01a6667b99f0e8ad4097af66c938e62
34 | https://conda.anaconda.org/conda-forge/noarch/certifi-2023.11.17-pyhd8ed1ab_0.conda#2011bcf45376341dd1d690263fdbc789
35 | https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.0-pyhd8ed1ab_0.conda#f6c211fee3c98229652b60a9a42ef363
36 | https://conda.anaconda.org/conda-forge/noarch/idna-3.6-pyhd8ed1ab_0.conda#1a76f09108576397c41c0b0c5bd84134
37 | https://conda.anaconda.org/conda-forge/noarch/natsort-8.4.0-pyhd8ed1ab_0.conda#70959cd1db3cf77b2a27a0836cfd08a7
38 | https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff
39 | https://conda.anaconda.org/conda-forge/noarch/robotframework-6.1.1-pyhd8ed1ab_1.conda#c7719c3e8bcabd3b30d989096a9268b3
40 | https://conda.anaconda.org/conda-forge/noarch/setuptools-68.2.2-pyhd8ed1ab_0.conda#fc2166155db840c634a1291a5c35a709
41 | https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.0-pyhd8ed1ab_0.tar.bz2#dd6cbc539e74cb1f430efbd4575b9303
42 | https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2#6d6552722448103793743dabfbda532d
43 | https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.9.0-pyha770c72_0.conda#a92a6440c3fe7052d63244f3aba2a4a7
44 | https://conda.anaconda.org/conda-forge/noarch/wheel-0.42.0-pyhd8ed1ab_0.conda#1cdea58981c5cbc17b51973bcaddcea7
45 | https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyhd8ed1ab_6.tar.bz2#30878ecc4bd36e8deeea1e3c151b2e0b
46 | https://conda.anaconda.org/conda-forge/win-64/cffi-1.16.0-py312he70551f_0.conda#5a51096925d52332c62bfd8904899055
47 | https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2#b21ed0883505ba1910994f1df031a428
48 | https://conda.anaconda.org/conda-forge/noarch/outcome-1.3.0.post0-pyhd8ed1ab_0.conda#c2954fba1935b5775755e3f14d951af0
49 | https://conda.anaconda.org/conda-forge/noarch/pip-23.3.1-pyhd8ed1ab_0.conda#2400c0b86889f43aa52067161e1fb108
50 | https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh0701188_6.tar.bz2#56cd9fe388baac0e90c7149cfac95b60
51 | https://conda.anaconda.org/conda-forge/noarch/robotframework-pythonlibcore-4.3.0-pyhd8ed1ab_0.conda#ee96d096541f6a89fb6d2a1ae21587ee
52 | https://conda.anaconda.org/conda-forge/noarch/robotframework-stacktrace-0.4.1-pyhd8ed1ab_0.tar.bz2#3dc788e294fd159537c931dbb964511e
53 | https://conda.anaconda.org/conda-forge/noarch/robotframework-pabot-2.16.0-pyhd8ed1ab_0.conda#d5cef1ba9df784f3d4633134a5e23b4e
54 | https://conda.anaconda.org/conda-forge/win-64/trio-0.23.1-py312h2e8e312_1.conda#7e6ab18b09fc3aa8020c6fb514920143
55 | https://conda.anaconda.org/conda-forge/noarch/urllib3-2.1.0-pyhd8ed1ab_0.conda#f8ced8ee63830dec7ecc1be048d1470a
56 | https://conda.anaconda.org/conda-forge/noarch/wsproto-1.2.0-pyhd8ed1ab_0.tar.bz2#00ba804b54f451d102f6a7615f08470d
57 | https://conda.anaconda.org/conda-forge/noarch/trio-websocket-0.11.1-pyhd8ed1ab_0.conda#020557a424faf98154938da4426fe987
58 | https://conda.anaconda.org/conda-forge/noarch/selenium-4.16.0-pyhd8ed1ab_0.conda#bdfe352529493afd8db9103df11e0bbf
59 | https://conda.anaconda.org/conda-forge/noarch/robotframework-seleniumlibrary-6.2.0-pyhd8ed1ab_0.conda#bef7db30c8d6d75e29318179f8fcee64
60 | https://conda.anaconda.org/conda-forge/noarch/robotframework-jupyterlibrary-0.5.0-pyhd8ed1ab_0.conda#f7b1b4f48c78c3feb041d7fb45d68910
61 |
--------------------------------------------------------------------------------
/packages/jupyterlab-fonts/src/tokens.ts:
--------------------------------------------------------------------------------
1 | import { ICommandPalette } from '@jupyterlab/apputils';
2 | import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook';
3 | import { CommandRegistry } from '@lumino/commands';
4 | import { Token } from '@lumino/coreutils';
5 | import { ISignal } from '@lumino/signaling';
6 | import { Menu } from '@lumino/widgets';
7 |
8 | import { IFontFacePrimitive } from './_schema';
9 | import * as SCHEMA from './schema';
10 |
11 | export type Scope = 'global' | 'notebook';
12 |
13 | export enum TextKind {
14 | code = 'code',
15 | content = 'content',
16 | ui = 'ui',
17 | }
18 |
19 | export const KIND_LABELS: { [key in TextKind]: string } = {
20 | code: 'Code',
21 | content: 'Content',
22 | ui: 'UI',
23 | };
24 |
25 | export enum FontFormat {
26 | woff2 = 'woff2',
27 | woff = 'woff',
28 | }
29 |
30 | export type TFontMimeTypes = { [key in FontFormat]: string };
31 |
32 | export const FONT_FORMATS = {
33 | woff2: 'font/woff2',
34 | woff: 'font/woff',
35 | };
36 |
37 | export type TextProperty = 'font-family' | 'font-size' | 'line-height';
38 |
39 | export interface IFontCallback {
40 | (): Promise;
41 | }
42 |
43 | export interface IFontLicense {
44 | name: string;
45 | spdx: string;
46 | text: () => Promise;
47 | holders: string[];
48 | }
49 |
50 | export interface IFontFaceOptions {
51 | name: string;
52 | faces: IFontCallback;
53 | license: IFontLicense;
54 | }
55 |
56 | export const CMD = {
57 | code: {
58 | fontSize: 'code-font-size',
59 | fontFamily: 'code-font-family',
60 | lineHeight: 'code-line-height',
61 | },
62 | content: {
63 | fontSize: 'content-font-size',
64 | fontFamily: 'content-font-family',
65 | lineHeight: 'content-line-height',
66 | },
67 | ui: {
68 | fontFamily: 'ui-font-family',
69 | },
70 | editFonts: 'font-editor:open',
71 | customFonts: {
72 | disable: 'custom-fonts:disable',
73 | enable: 'custom-fonts:enable',
74 | },
75 | };
76 |
77 | export const ROOT = ':root';
78 |
79 | export type ICSSVars = {
80 | [key in TextKind]: { [key in TextProperty]?: SCHEMA.ICSSOM };
81 | };
82 |
83 | export const CSS: ICSSVars = {
84 | code: {
85 | 'font-family': '--jp-code-font-family',
86 | 'font-size': '--jp-code-font-size',
87 | 'line-height': '--jp-code-line-height',
88 | },
89 | content: {
90 | 'font-family': '--jp-content-font-family',
91 | 'font-size': '--jp-content-font-size1',
92 | 'line-height': '--jp-content-line-height',
93 | },
94 | ui: {
95 | 'font-family': '--jp-ui-font-family',
96 | },
97 | };
98 |
99 | export namespace DOM {
100 | export const sheet = 'jp-Fonts-Sheet';
101 | export const modGlobal = 'jp-fonts-mod-global';
102 | export const modNotebook = 'jp-fonts-mod-notebook';
103 | export const notebookPanel = 'jp-NotebookPanel';
104 | export const cell = 'jp-Cell';
105 | }
106 |
107 | export type ICSSTextOptions = {
108 | [key in TextProperty]: (manager: IFontManager) => SCHEMA.ICSSOM[];
109 | };
110 |
111 | export const TEXT_OPTIONS: ICSSTextOptions = {
112 | 'font-size': (_m) => Array.from(Array(25).keys()).map((i) => `${i + 8}px`),
113 | 'line-height': (_m) => Array.from(Array(8).keys()).map((i) => `${i * 0.25 + 1}`),
114 | 'font-family': (m) => {
115 | let names = Array.from(m.fonts.values()).reduce((memo, f) => {
116 | return memo.concat(f.name);
117 | }, [] as string[]);
118 | names.sort((a, b) => a.localeCompare(b));
119 | return names;
120 | },
121 | };
122 |
123 | export type ICSSTextLabels = { [key in TextProperty]: string };
124 |
125 | export const TEXT_LABELS: ICSSTextLabels = {
126 | 'font-size': 'Size',
127 | 'line-height': 'Line Height',
128 | 'font-family': 'Font',
129 | };
130 |
131 | export const DEFAULT = {
132 | code: {
133 | fontSize: '13px',
134 | lineHeight: '1',
135 | fontFamily: '"Source Code Pro", monospace',
136 | },
137 | };
138 |
139 | export const PACKAGE_NAME: string = '@deathbeds/jupyterlab-fonts';
140 | export const CONFIGURED_CLASS = 'jp-fonts-configured';
141 |
142 | export const IFontManager = new Token(
143 | '@deathbeds/jupyterlab-fonts:IFontManager',
144 | );
145 |
146 | export interface IFontManagerConstructor {
147 | new (
148 | commands: CommandRegistry,
149 | palette: ICommandPalette,
150 | notebooks: INotebookTracker,
151 | ): IFontManager;
152 | }
153 |
154 | export interface IFontManager {
155 | ready: Promise;
156 | registerFontFace(options: IFontFaceOptions): void;
157 | licensePaneRequested: ISignal;
158 | requestLicensePane(font: any): void;
159 | fonts: Map;
160 | stylesheets: HTMLStyleElement[];
161 | menu: Menu;
162 | getVarName(property: TextProperty, options: ITextStyleOptions): SCHEMA.ICSSOM | null;
163 | getTextStyle(
164 | property: TextProperty,
165 | options: ITextStyleOptions,
166 | ): SCHEMA.ICSSOM | null;
167 | setTextStyle(
168 | property: TextProperty,
169 | value: SCHEMA.ICSSOM | null,
170 | options: ITextStyleOptions,
171 | ): void;
172 | dataURISrc(url: string, format: FontFormat): Promise;
173 | setTransientNotebookStyle(panel: NotebookPanel, style: SCHEMA.ISettings | null): void;
174 | getTransientNotebookStyle(panel: NotebookPanel): SCHEMA.ISettings | null;
175 | ensureJss(): Promise;
176 | }
177 |
178 | export interface ITextStyleOptions {
179 | kind: TextKind;
180 | scope?: Scope;
181 | notebook?: NotebookPanel;
182 | }
183 |
184 | export interface IMakeFaceOptions {
185 | name: string;
186 | variant: string;
187 | woff2(): Promise;
188 | primitive?: Partial;
189 | }
190 |
191 | export interface IPluginVariantOptions {
192 | woff2(): Promise;
193 | style?: Omit;
194 | }
195 |
196 | export interface IPluginOptions {
197 | id: string;
198 | fontName: string;
199 | license: Omit;
200 | licenseText(): Promise;
201 | variants(): Promise>;
202 | }
203 |
--------------------------------------------------------------------------------
/.github/locks/atest_linux-64.conda.lock:
--------------------------------------------------------------------------------
1 | # channels:
2 | # - conda-forge
3 | # - nodefaults
4 | # dependencies:
5 | # - firefox >=115,<116
6 | # - geckodriver
7 | # - pip
8 | # - robotframework >=6.1
9 | # - robotframework-jupyterlibrary >=0.5.0
10 | # - robotframework-pabot
11 |
12 | @EXPLICIT
13 | https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81
14 | https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2023.11.17-hbcca054_0.conda#01ffc8d36f9eba0ce0b3c1955fa780ee
15 | https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h41732ed_0.conda#7aca3059a1729aa76c597603f10b0dd3
16 | https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-13.2.0-h7e041cc_3.conda#937eaed008f6bf2191c5fe76f87755e9
17 | https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-4_cp312.conda#dccc2d142812964fcc6abdc97b672dff
18 | https://conda.anaconda.org/conda-forge/noarch/tzdata-2023c-h71feb2d_0.conda#939e3e74d8be4dac89ce83b20de2492a
19 | https://conda.anaconda.org/conda-forge/linux-64/libgomp-13.2.0-h807b86a_3.conda#7124cbb46b13d395bdde68f2d215c989
20 | https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d
21 | https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-13.2.0-h807b86a_3.conda#23fdf1fef05baeb7eadc2aed5fb0011f
22 | https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hd590300_5.conda#69b8b6202a07720f448be700e300ccf4
23 | https://conda.anaconda.org/conda-forge/linux-64/firefox-115.5.0esr-hd3aeb46_0.conda#057d800456c7fca5920499195e0a8be5
24 | https://conda.anaconda.org/conda-forge/linux-64/geckodriver-0.33.0-h0e8d75e_1.conda#882fc01227e3b0e034a335f8fc13e0b2
25 | https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.5.0-hcb278e6_1.conda#6305a3dd2752c76335295da4e581f2fd
26 | https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3
27 | https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda#30fd6e37fe21f86f4bd26d6ee73eeec7
28 | https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda#40b61aab5c7ba9ff276c41cfffe6b80b
29 | https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-hd590300_5.conda#f36c115f1ee199da648e0597ec2047ad
30 | https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.4-h59595ed_2.conda#7dbaa197d7ba6032caf7ae7f32c1efa0
31 | https://conda.anaconda.org/conda-forge/linux-64/openssl-3.2.0-hd590300_1.conda#603827b39ea2b835268adb8c821b8570
32 | https://conda.anaconda.org/conda-forge/linux-64/selenium-manager-4.16.0-he8a937b_0.conda#64456173e19a30f40f18d0d58e895602
33 | https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0
34 | https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.44.2-h2797004_0.conda#3b6a9f225c3dbe0d24f4fedd4625c5bf
35 | https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda#47d31b792659ce70f470b5c82fdfb7a4
36 | https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda#d453b98d9c83e71da0741bb0ff4d76bc
37 | https://conda.anaconda.org/conda-forge/linux-64/python-3.12.0-hab00c5b_0_cpython.conda#7f97faab5bebcc2580f4f299285323da
38 | https://conda.anaconda.org/conda-forge/noarch/attrs-23.1.0-pyh71513ae_1.conda#3edfead7cedd1ab4400a6c588f3e75f8
39 | https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h30efb56_1.conda#45801a89533d3336a365284d93298e36
40 | https://conda.anaconda.org/conda-forge/noarch/certifi-2023.11.17-pyhd8ed1ab_0.conda#2011bcf45376341dd1d690263fdbc789
41 | https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.0-pyhd8ed1ab_0.conda#f6c211fee3c98229652b60a9a42ef363
42 | https://conda.anaconda.org/conda-forge/noarch/idna-3.6-pyhd8ed1ab_0.conda#1a76f09108576397c41c0b0c5bd84134
43 | https://conda.anaconda.org/conda-forge/noarch/natsort-8.4.0-pyhd8ed1ab_0.conda#70959cd1db3cf77b2a27a0836cfd08a7
44 | https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025
45 | https://conda.anaconda.org/conda-forge/noarch/robotframework-6.1.1-pyhd8ed1ab_1.conda#c7719c3e8bcabd3b30d989096a9268b3
46 | https://conda.anaconda.org/conda-forge/noarch/setuptools-68.2.2-pyhd8ed1ab_0.conda#fc2166155db840c634a1291a5c35a709
47 | https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.0-pyhd8ed1ab_0.tar.bz2#dd6cbc539e74cb1f430efbd4575b9303
48 | https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2#6d6552722448103793743dabfbda532d
49 | https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.9.0-pyha770c72_0.conda#a92a6440c3fe7052d63244f3aba2a4a7
50 | https://conda.anaconda.org/conda-forge/noarch/wheel-0.42.0-pyhd8ed1ab_0.conda#1cdea58981c5cbc17b51973bcaddcea7
51 | https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2#b21ed0883505ba1910994f1df031a428
52 | https://conda.anaconda.org/conda-forge/noarch/outcome-1.3.0.post0-pyhd8ed1ab_0.conda#c2954fba1935b5775755e3f14d951af0
53 | https://conda.anaconda.org/conda-forge/noarch/pip-23.3.1-pyhd8ed1ab_0.conda#2400c0b86889f43aa52067161e1fb108
54 | https://conda.anaconda.org/conda-forge/noarch/robotframework-pythonlibcore-4.3.0-pyhd8ed1ab_0.conda#ee96d096541f6a89fb6d2a1ae21587ee
55 | https://conda.anaconda.org/conda-forge/noarch/robotframework-stacktrace-0.4.1-pyhd8ed1ab_0.tar.bz2#3dc788e294fd159537c931dbb964511e
56 | https://conda.anaconda.org/conda-forge/noarch/urllib3-2.1.0-pyhd8ed1ab_0.conda#f8ced8ee63830dec7ecc1be048d1470a
57 | https://conda.anaconda.org/conda-forge/noarch/robotframework-pabot-2.16.0-pyhd8ed1ab_0.conda#d5cef1ba9df784f3d4633134a5e23b4e
58 | https://conda.anaconda.org/conda-forge/linux-64/trio-0.23.1-py312h7900ff3_1.conda#5038ccca69c2d716d8ddba4d75ec6098
59 | https://conda.anaconda.org/conda-forge/noarch/wsproto-1.2.0-pyhd8ed1ab_0.tar.bz2#00ba804b54f451d102f6a7615f08470d
60 | https://conda.anaconda.org/conda-forge/noarch/trio-websocket-0.11.1-pyhd8ed1ab_0.conda#020557a424faf98154938da4426fe987
61 | https://conda.anaconda.org/conda-forge/noarch/selenium-4.16.0-pyhd8ed1ab_0.conda#bdfe352529493afd8db9103df11e0bbf
62 | https://conda.anaconda.org/conda-forge/noarch/robotframework-seleniumlibrary-6.2.0-pyhd8ed1ab_0.conda#bef7db30c8d6d75e29318179f8fcee64
63 | https://conda.anaconda.org/conda-forge/noarch/robotframework-jupyterlibrary-0.5.0-pyhd8ed1ab_0.conda#f7b1b4f48c78c3feb041d7fb45d68910
64 |
--------------------------------------------------------------------------------
/packages/jupyterlab-fonts/schema/fonts.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Fonts",
3 | "description": "Settings for JupyterLab Fonts",
4 | "type": "object",
5 | "jupyter.lab.setting-icon": "fonts:fonts",
6 | "jupyter.lab.setting-icon-label": "Fonts",
7 | "definitions": {
8 | "ICSSOM": {
9 | "oneOf": [
10 | { "$ref": "#/definitions/ICSSOMPrimitive" },
11 | {
12 | "type": "array",
13 | "items": { "anyOf": [{ "$ref": "#/definitions/ICSSOMPrimitive" }] }
14 | },
15 | {
16 | "type": "object",
17 | "patternProperties": {
18 | ".*": { "$ref": "#/definitions/ICSSOM" }
19 | }
20 | }
21 | ]
22 | },
23 | "ICSSOMPrimitive": {
24 | "oneOf": [{ "type": "string" }, { "type": "number" }]
25 | },
26 | "IFontFaceCommon": {
27 | "type": "object",
28 | "required": ["src"],
29 | "properties": {
30 | "src": { "$ref": "#/definitions/ICSSOM" },
31 | "unicode-range": { "$ref": "#/definitions/ICSSOM" },
32 | "font-variant": { "$ref": "#/definitions/ICSSOM" },
33 | "font-feature-settings": { "$ref": "#/definitions/ICSSOM" },
34 | "font-variation-settings": { "$ref": "#/definitions/ICSSOM" },
35 | "font-stretch": { "$ref": "#/definitions/ICSSOM" },
36 | "font-weight": { "$ref": "#/definitions/ICSSOM" },
37 | "font-style": { "$ref": "#/definitions/ICSSOM" },
38 | "unicodeRange": { "$ref": "#/definitions/ICSSOM" },
39 | "fontVariant": { "$ref": "#/definitions/ICSSOM" },
40 | "fontFeatureSettings": { "$ref": "#/definitions/ICSSOM" },
41 | "fontVariationSettings": { "$ref": "#/definitions/ICSSOM" },
42 | "fontStretch": { "$ref": "#/definitions/ICSSOM" },
43 | "fontWeight": { "$ref": "#/definitions/ICSSOM" },
44 | "fontStyle": { "$ref": "#/definitions/ICSSOM" }
45 | }
46 | },
47 | "IFontFaceCamel": {
48 | "allOf": [
49 | {
50 | "type": "object",
51 | "required": ["fontFamily"],
52 | "properties": {
53 | "fontFamily": { "type": "string" }
54 | }
55 | },
56 | { "$ref": "#/definitions/IFontFaceCommon" }
57 | ]
58 | },
59 | "IFontFaceCanonical": {
60 | "allOf": [
61 | {
62 | "type": "object",
63 | "required": ["font-family"],
64 | "properties": {
65 | "font-family": { "type": "string" }
66 | }
67 | },
68 | { "$ref": "#/definitions/IFontFaceCommon" }
69 | ]
70 | },
71 | "IFontFacePrimitive": {
72 | "oneOf": [
73 | { "$ref": "#/definitions/IFontFaceCamel" },
74 | { "$ref": "#/definitions/IFontFaceCanonical" }
75 | ]
76 | },
77 | "IFontFaceObject": {
78 | "type": "object",
79 | "patternProperties": {
80 | ".*": {
81 | "type": "array",
82 | "items": {
83 | "$ref": "#/definitions/IFontFacePrimitive"
84 | }
85 | }
86 | },
87 | "additionalProperties": {
88 | "type": "array",
89 | "items": {
90 | "$ref": "#/definitions/IFontFacePrimitive"
91 | }
92 | }
93 | },
94 | "IFontFace": {
95 | "oneOf": [
96 | {
97 | "type": "array",
98 | "items": { "$ref": "#/definitions/IFontFacePrimitive" }
99 | },
100 | { "$ref": "#/definitions/IFontFacePrimitive" }
101 | ]
102 | },
103 | "IFontLicenseObject": {
104 | "type": "object",
105 | "patternProperties": {
106 | ".*": {
107 | "$ref": "#/definitions/IFontLicensePrimitive"
108 | }
109 | },
110 | "additionalProperties": {
111 | "$ref": "#/definitions/IFontLicensePrimitive"
112 | }
113 | },
114 | "IFontLicensePrimitive": {
115 | "type": "object",
116 | "required": ["name", "spdx", "text", "holders"],
117 | "properties": {
118 | "name": { "type": "string" },
119 | "spdx": { "type": "string" },
120 | "text": { "type": "string" },
121 | "holders": {
122 | "type": "array",
123 | "items": {
124 | "type": "string"
125 | }
126 | }
127 | }
128 | },
129 | "IJSS": {
130 | "type": "object",
131 | "additionalProperties": {
132 | "oneOf": [{ "$ref": "#/definitions/IJSS" }, { "#ref": "#/definitions/ICSSOM" }]
133 | }
134 | },
135 | "IJSSRoot": {
136 | "type": "object",
137 | "additionalProperties": {
138 | "$ref": "#/definitions/ICSSOM"
139 | },
140 | "patternProperties": {
141 | ".*": {
142 | "$ref": "#/definitions/ICSSOM"
143 | }
144 | }
145 | },
146 | "IStyles": {
147 | "type": "object",
148 | "additionalProperties": {
149 | "anyOf": [
150 | { "$ref": "#/definitions/ICSSOM" },
151 | { "$ref": "#/definitions/IJSS" },
152 | { "$ref": "#/definitions/IFontFace" },
153 | { "$ref": "#/definitions/IJSSRoot" }
154 | ]
155 | },
156 | "properties": {
157 | "@font-face": {
158 | "$ref": "#/definitions/IFontFace"
159 | },
160 | ":root": {
161 | "$ref": "#/definitions/IJSSRoot"
162 | }
163 | }
164 | }
165 | },
166 |
167 | "properties": {
168 | "enabled": {
169 | "description": "Enable all font customizations",
170 | "title": "Enable Custom Fonts",
171 | "type": "boolean",
172 | "default": true
173 | },
174 | "version": {
175 | "description": "Reserved for future use to provide backwards compatibility",
176 | "default": "v1",
177 | "title": "Configuration Version",
178 | "type": "string"
179 | },
180 | "styles": {
181 | "description": "JSS-compatible JSON applied to the Global scope",
182 | "default": {},
183 | "title": "Global Styles",
184 | "$ref": "#/definitions/IStyles"
185 | },
186 | "fonts": {
187 | "description": "Embedded JSS `@font-face` declarations grouped by `font-family`",
188 | "default": {},
189 | "title": "Embedded Fonts",
190 | "$ref": "#/definitions/IFontFaceObject"
191 | },
192 | "fontLicenses": {
193 | "description": "Rights for embedded fonts",
194 | "default": {},
195 | "title": "Embedded Font Licenses",
196 | "$ref": "#/definitions/IFontLicenseObject"
197 | }
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/examples/index.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "110881cb-5284-4d04-88a3-90c9a5f29909",
6 | "metadata": {
7 | "editable": true,
8 | "slideshow": {
9 | "slide_type": ""
10 | },
11 | "tags": [
12 | "sticky"
13 | ]
14 | },
15 | "source": [
16 | "# hello `jupyterlab-fonts`"
17 | ]
18 | },
19 | {
20 | "cell_type": "markdown",
21 | "id": "39251a21-2151-46ff-908e-e6facb8fa03a",
22 | "metadata": {
23 | "editable": true,
24 | "slideshow": {
25 | "slide_type": ""
26 | },
27 | "tags": []
28 | },
29 | "source": [
30 | "`jupyterlab-fonts` provides a subset of [JSS](https://cssinjs.org), stored in JupyterLab settings and Notebook metadata."
31 | ]
32 | },
33 | {
34 | "cell_type": "markdown",
35 | "id": "cfe7980f-aadf-45d4-98ec-23899d1b3075",
36 | "metadata": {},
37 | "source": [
38 | "## In Settings\n",
39 | "\n",
40 | "The entire JupyterLab UI can be configured through the _Advanced JSON Settings Editor_."
41 | ]
42 | },
43 | {
44 | "cell_type": "markdown",
45 | "id": "3ba0140f-2548-4093-a841-2b10da2393ff",
46 | "metadata": {},
47 | "source": [
48 | "## In Notebooks\n",
49 | "\n",
50 | "Notebooks can be styled in the _Property Inspector_ at both the _Notebook_ and _Cell_ level.\n",
51 | "\n",
52 | "These styles will only propagate to the notebook that defines them."
53 | ]
54 | },
55 | {
56 | "cell_type": "markdown",
57 | "id": "9e0b33c4-f4c0-45a7-8809-8afc1196fa12",
58 | "metadata": {},
59 | "source": [
60 | "### `data-jpf-*`\n",
61 | "\n",
62 | "To enable rich, portable, and isolated customization, a number of DOM attributes are added."
63 | ]
64 | },
65 | {
66 | "cell_type": "markdown",
67 | "id": "98c268bd-615a-4886-94ba-509b918d9d7b",
68 | "metadata": {},
69 | "source": [
70 | "#### `data-jpf-cell-id`\n",
71 | "\n",
72 | "Each notebook cell is given its id, as provided in the `cell_id` attribute."
73 | ]
74 | },
75 | {
76 | "cell_type": "markdown",
77 | "id": "a2ce5767-0919-4b7b-8d36-390327c46155",
78 | "metadata": {},
79 | "source": [
80 | "#### `data-jpf-tags`\n",
81 | "\n",
82 | "Each notebook cell is given its cell metadata `tags`, as a sorted, comma-delimited list (with leading and trailing commas). This allows for composing tag selectors with the `*=` operator."
83 | ]
84 | },
85 | {
86 | "cell_type": "markdown",
87 | "id": "414d68e9-5fe2-45e0-a4fd-5946ba60a34e",
88 | "metadata": {},
89 | "source": [
90 | "##### Escaping `,`\n",
91 | "\n",
92 | "Unfortunately, when used in attribute selectors, the comma character `,` must be escaped as `\\2C`, which, in JSON, must be _further_ escaped as `\\\\2C`. For example, this notebook usese a `sticky` tag:\n",
93 | "\n",
94 | "```json\n",
95 | "\"[data-jpf-cell-tags*='\\\\2Csticky\\\\2C'] .jp-RenderedMarkdown\": {\n",
96 | " // ...\n",
97 | "}\n",
98 | "```"
99 | ]
100 | },
101 | {
102 | "cell_type": "markdown",
103 | "id": "98f53d4a-09e3-42c5-84ee-0859272e22a4",
104 | "metadata": {
105 | "@deathbeds/jupyterlab-fonts": {
106 | "styles": {}
107 | },
108 | "editable": true,
109 | "slideshow": {
110 | "slide_type": ""
111 | },
112 | "tags": [
113 | "sticky"
114 | ]
115 | },
116 | "source": [
117 | "`jupyterlab-fonts` doesn't _yet_ support Jupyter Widgets integration. However, using CSS Variables, it is possible to capture configurable style rules in a given notebook, and modify them via dynamic content."
118 | ]
119 | },
120 | {
121 | "cell_type": "markdown",
122 | "id": "dd627bf9-660e-44b9-9d5c-f33877b253cb",
123 | "metadata": {
124 | "editable": true,
125 | "slideshow": {
126 | "slide_type": ""
127 | },
128 | "tags": []
129 | },
130 | "source": [
131 | "### Interacting with Widgets"
132 | ]
133 | },
134 | {
135 | "cell_type": "code",
136 | "execution_count": null,
137 | "id": "5118685f-762b-4c65-9910-5b19cec94f8e",
138 | "metadata": {
139 | "editable": true,
140 | "slideshow": {
141 | "slide_type": ""
142 | },
143 | "tags": []
144 | },
145 | "outputs": [],
146 | "source": [
147 | "from ipywidgets import *"
148 | ]
149 | },
150 | {
151 | "cell_type": "code",
152 | "execution_count": null,
153 | "id": "7ddee1a9-73d6-45da-b53f-bbada3275952",
154 | "metadata": {
155 | "editable": true,
156 | "slideshow": {
157 | "slide_type": ""
158 | },
159 | "tags": []
160 | },
161 | "outputs": [],
162 | "source": [
163 | "style = HTML()\n",
164 | "my_number = FloatSlider(description=\"--my-var\", min=0, max=10)\n",
165 | "my_z = SelectionSlider(options=[0, 1, 2, 4, 6, 8, 12, 16, 20, 24], description=\"--my-z\")"
166 | ]
167 | },
168 | {
169 | "cell_type": "code",
170 | "execution_count": null,
171 | "id": "141bcdb0-d870-44f4-83ad-73136d7b1d1e",
172 | "metadata": {
173 | "editable": true,
174 | "slideshow": {
175 | "slide_type": ""
176 | },
177 | "tags": []
178 | },
179 | "outputs": [],
180 | "source": [
181 | "def update_style(*_):\n",
182 | " return f\"\"\"\"\"\"\n",
186 | "dlink((my_number, \"value\"), (style, \"value\"), update_style);\n",
187 | "dlink((my_z, \"value\"), (style, \"value\"), update_style);"
188 | ]
189 | },
190 | {
191 | "cell_type": "code",
192 | "execution_count": null,
193 | "id": "67d9fec7-75ea-4a36-85e1-c3e2d66c9434",
194 | "metadata": {
195 | "editable": true,
196 | "slideshow": {
197 | "slide_type": ""
198 | },
199 | "tags": []
200 | },
201 | "outputs": [],
202 | "source": [
203 | "HBox([my_number, my_z, style])"
204 | ]
205 | }
206 | ],
207 | "metadata": {
208 | "@deathbeds/jupyterlab-fonts": {
209 | "styles": {
210 | "--my-var": "calc(var(--my-number) * var(--jp-content-font-size0))",
211 | ".jupyter-widgets": {
212 | "border-radius": "var(--my-var)",
213 | "box-shadow": "var(--my-z)",
214 | "height": "unset",
215 | "margin": "var(--my-var)",
216 | "padding": "var(--my-var)"
217 | },
218 | "[data-jpf-cell-tags*='\\2Csticky\\2C'] .jp-RenderedMarkdown": {
219 | "background-color": "#fff4bf",
220 | "border": 0,
221 | "box-shadow": "var(--my-z)",
222 | "display": "block",
223 | "height": "16em",
224 | "padding": "1em",
225 | "position": "fixed",
226 | "right": "2em",
227 | "transform": "rotateZ(4deg)",
228 | "width": "16em",
229 | "z-index": 999
230 | }
231 | }
232 | },
233 | "kernelspec": {
234 | "display_name": "Python 3 (ipykernel)",
235 | "language": "python",
236 | "name": "python3"
237 | },
238 | "language_info": {
239 | "codemirror_mode": {
240 | "name": "ipython",
241 | "version": 3
242 | },
243 | "file_extension": ".py",
244 | "mimetype": "text/x-python",
245 | "name": "python",
246 | "nbconvert_exporter": "python",
247 | "pygments_lexer": "ipython3",
248 | "version": "3.11.5"
249 | }
250 | },
251 | "nbformat": 4,
252 | "nbformat_minor": 5
253 | }
254 |
--------------------------------------------------------------------------------
/.github/specs/__lock__.py:
--------------------------------------------------------------------------------
1 | """Actions for working with conda-lock."""
2 | import shutil
3 | import subprocess
4 | import tempfile
5 | import textwrap
6 | from functools import lru_cache
7 | from itertools import product
8 | from pathlib import Path
9 | from typing import Any, Dict, List, Optional
10 |
11 | import yaml
12 |
13 | UTF8 = {"encoding": "utf-8"}
14 | JSON_FMT = {"indent": 2, "sort_keys": True}
15 | EXPLICIT = "@EXPLICIT"
16 |
17 |
18 | @lru_cache(1000)
19 | def safe_load(path: Path) -> Dict[str, Any]:
20 | """Load and cache some YAML."""
21 | return yaml.safe_load(path.read_bytes())
22 |
23 |
24 | def iter_spec_stacks(spec_path, platform):
25 | """Generate ``environment.yml``."""
26 | spec = safe_load(spec_path)
27 | # initialize the stacks
28 | base_stack = [spec_path]
29 | stacks = [base_stack]
30 |
31 | platforms = spec.get("platforms")
32 |
33 | if platforms and platform not in platforms:
34 | return
35 |
36 | for inherit in spec.get("_inherit_from", []):
37 | substacks = [*iter_spec_stacks(spec_path.parent / inherit, platform)]
38 | if substacks:
39 | stacks = [[*stack, *substack] for substack in substacks for stack in stacks]
40 |
41 | factors = [
42 | sorted((spec_path.parent / factor).glob("*.yml"))
43 | for factor in spec.get("_matrix", [])
44 | ]
45 |
46 | if factors:
47 | matrix_stacks = []
48 | for row in product(*factors):
49 | matrix_stacks += [
50 | sum(
51 | [
52 | substack
53 | for factor in row
54 | for substack in iter_spec_stacks(factor, platform)
55 | ],
56 | [],
57 | ),
58 | ]
59 | stacks = [
60 | [*stack, *matrix_stack]
61 | for matrix_stack in matrix_stacks
62 | for stack in stacks
63 | ]
64 |
65 | yield from stacks
66 |
67 |
68 | class IndentDumper(yaml.SafeDumper):
69 |
70 | """Safe dump with indenting."""
71 |
72 | def increase_indent(self, flow=None, indentless=None):
73 | """Add more indentation."""
74 | flow = True if flow is None else flow
75 | indentless = False
76 | return super().increase_indent(flow=flow, indentless=indentless)
77 |
78 |
79 | def merge_envs(env_path: Optional[Path], stack: List[Path]) -> Optional[str]:
80 | """Create a normalized set of dependencies from a stack of files."""
81 | env = {"channels": [], "dependencies": []}
82 |
83 | for stack_yml in stack:
84 | stack_data = safe_load(stack_yml)
85 | env["channels"] = stack_data.get("channels") or env["channels"]
86 | if "dependencies" not in stack_data:
87 | msg = f"{stack_yml.name} needs 'dependencies'"
88 | raise ValueError(msg)
89 | env["dependencies"] += stack_data["dependencies"]
90 |
91 | env["dependencies"] = sorted(set(env["dependencies"]))
92 |
93 | env_str = yaml.dump(env, Dumper=IndentDumper)
94 |
95 | if env_path:
96 | env_path.write_text(env_str, **UTF8)
97 | return None
98 |
99 | return env_str
100 |
101 |
102 | def lock_comment(stack: List[Path], indent="# ") -> str:
103 | """Generate a lockfile header comment."""
104 | return textwrap.indent(merge_envs(None, stack), indent)
105 |
106 |
107 | def needs_lock(lockfile: Path, stack: List[Path]) -> bool:
108 | """Determine whether the lockfile is up-to-date."""
109 | if not lockfile.exists():
110 | return True
111 | lock_text = lockfile.read_text(**UTF8)
112 | comment = lock_comment(stack)
113 | return comment not in lock_text
114 |
115 |
116 | def lock_one(platform: str, lockfile: Path, stack: List[Path]) -> bool:
117 | """Lock one path, based on its input env stack."""
118 | if not needs_lock(lockfile, stack):
119 | print(f" --- lockfile up-to-date: {lockfile}")
120 | return True
121 |
122 | print(f" ... updating: {lockfile}")
123 |
124 | if not lockfile.parent.exists():
125 | print(f" ... creating {lockfile.parent}")
126 | lockfile.parent.mkdir(parents=True)
127 |
128 | comment = lock_comment(stack)
129 |
130 | for solver in [["--mamba"], ["--no-mamba"]]:
131 | lock_args = ["conda-lock", *solver, "--kind=explicit"]
132 | for env_file in stack:
133 | lock_args += ["--file", env_file]
134 | lock_args += [f"--platform={platform}"]
135 |
136 | rc = 1
137 |
138 | with tempfile.TemporaryDirectory() as td:
139 | tdp = Path(td)
140 | tmp_lock = tdp / f"conda-{platform}.lock"
141 | str_args = list(map(str, lock_args))
142 | print(f""" >>> {" ".join(str_args)}""")
143 | rc = subprocess.call(str_args, cwd=td)
144 | print(f" ... STATUS {rc}")
145 | if rc != 0:
146 | continue
147 | raw = tmp_lock.read_text(**UTF8).split(EXPLICIT)[1].strip()
148 | lockfile.write_text("\n".join([comment, EXPLICIT, raw, ""]), **UTF8)
149 | print(f" ... OK {lockfile}")
150 | return True
151 |
152 | print(f" !!! FAIL {lockfile}")
153 | return False
154 | return None
155 |
156 |
157 | def lock_stem(subdir, stack: List[Path]) -> str:
158 | """Calculate the lockfile/env name stem."""
159 | first = stack[0]
160 | return "_".join(
161 | [first.stem, subdir] + [s.stem for s in stack if s.parent != first.parent],
162 | )
163 |
164 |
165 | def preflight(
166 | preflight_yml: str,
167 | *lock_specs_yml: str,
168 | subdirs: List[str],
169 | ) -> Optional[bool]:
170 | """Generate a preflight yaml and all of the headers."""
171 | preflight = Path(preflight_yml)
172 | lock_build = preflight.parent
173 | if lock_build.exists():
174 | shutil.rmtree(preflight.parent)
175 | preflight.parent.mkdir(parents=True)
176 | specs = [Path(p) for p in lock_specs_yml]
177 | all_info = {}
178 | for spec_path in specs:
179 | for subdir in subdirs:
180 | for stack in iter_spec_stacks(spec_path, subdir):
181 | first = safe_load(stack[0])
182 | target = first.get("_target")
183 | if target:
184 | Path(target).write_text(lock_comment(stack, ""), **UTF8)
185 | stem = lock_stem(subdir, stack)
186 | txt = lock_build / f"{stem}.txt"
187 | txt.write_text(lock_comment(stack), **UTF8)
188 | all_info[stem] = {
189 | "subdir": subdir,
190 | "stack": [str(p) for p in stack],
191 | }
192 | preflight.write_text(yaml.safe_dump(dict(sorted(all_info.items()))))
193 |
194 |
195 | def lock_from_preflight(lock_dir: str, header_path: str, preflight_path: str) -> None:
196 | """Generate a single lockfile from the preflight file."""
197 | preflight = safe_load(Path(preflight_path))
198 | header = Path(header_path)
199 | header_txt = header.read_text(**UTF8)
200 | info = preflight[header.stem]
201 | lockfile = Path(lock_dir) / f"{header.stem}.conda.lock"
202 | if lockfile.exists() and header_txt in lockfile.read_text(**UTF8):
203 | print(f" --- up-to-date: {lockfile}")
204 | return True
205 | print(" ... solving:")
206 | print(textwrap.indent(yaml.safe_dump(info), " "))
207 | return lock_one(info["subdir"], lockfile, [Path(p) for p in info["stack"]])
208 |
--------------------------------------------------------------------------------
/scripts/actions.py:
--------------------------------------------------------------------------------
1 | """Cargo-culted actions for use with ``doitoml``."""
2 |
3 | import json
4 | import os
5 | import shutil
6 | import subprocess
7 | from hashlib import sha256
8 | from pathlib import Path
9 |
10 | UTF8 = {"encoding": "utf-8"}
11 | JSON_FMT = {"indent": 2, "sort_keys": True}
12 |
13 |
14 | # new actions that might move out
15 | def splice_json(key: str, src: str, dest: str):
16 | """Copy a single key from one JSON file to another."""
17 | src_json = json.load(Path(src).open())
18 | dest_path = Path(dest)
19 | dest_json = json.load(dest_path.open())
20 | dest_json[key] = src_json[key]
21 | dest_path.write_text(json.dumps(dest_json, **JSON_FMT), **UTF8)
22 |
23 |
24 | def source_date_epoch():
25 | """Fetch the git commit date for reproducible builds."""
26 | return (
27 | subprocess.check_output(["git", "log", "-1", "--format=%ct"])
28 | .decode("utf-8")
29 | .strip()
30 | )
31 |
32 |
33 | def git_info():
34 | """Dump some git info."""
35 | print(json.dumps({"SOURCE_DATE_EPOCH": source_date_epoch()}))
36 |
37 |
38 | def merge_json(src_path: str, dest_path: str):
39 | """Do a dumb merge of two JSON files."""
40 | src = Path(src_path)
41 | dest = Path(dest_path)
42 |
43 | if not dest.parent.exists():
44 | dest.parent.mkdir(parents=True)
45 |
46 | old_data = {} if not dest.exists() else json.load(dest.open())
47 | new_data = dict(**old_data)
48 | new_data.update(json.load(src.open()))
49 |
50 | new_data_text = json.dumps(new_data, **JSON_FMT)
51 | old_data_text = json.dumps(old_data, **JSON_FMT)
52 |
53 | if new_data_text != old_data_text:
54 | dest.write_text(new_data_text)
55 |
56 |
57 | def hash_some(hash_file, *hash_inputs):
58 | """Write a hashfile of the given inputs."""
59 | hash_path = Path(hash_file)
60 | input_paths = [Path(hi) for hi in hash_inputs]
61 | if hash_path.exists():
62 | hash_path.unlink()
63 |
64 | lines = []
65 |
66 | for p in sorted(input_paths):
67 | lines += [" ".join([sha256(p.read_bytes()).hexdigest(), p.name])]
68 |
69 | output = "\n".join(lines)
70 | print(output)
71 | hash_path.write_text(output, encoding="utf-8")
72 |
73 |
74 | def clean_some(*paths) -> bool:
75 | """Clean up some paths."""
76 | for path in [Path(p) for p in paths]:
77 | if path.is_dir():
78 | shutil.rmtree(path)
79 | elif path.exists():
80 | path.unlink()
81 | return True
82 |
83 |
84 | def run(*args, ok_rc=None):
85 | """Run something, maybe allowing non-zero return codes."""
86 | rc = subprocess.call(list(args), shell=False)
87 | return str(rc) in ok_rc or ["0"]
88 |
89 |
90 | def maybe_atest_one(
91 | conda_run,
92 | attempt,
93 | last_attempt,
94 | out_dir,
95 | prev_out,
96 | atest_dir,
97 | jscov,
98 | atest_args=None,
99 | ):
100 | """Maybe run the robot test suite, if the previous attempt failed."""
101 | is_ok = "0"
102 | rc_name = "robot.rc"
103 | rc_path = Path(out_dir[0]) / rc_name
104 |
105 | dry_run = not attempt
106 |
107 | env = dict(os.environ)
108 |
109 | if "MOZ_HEADLESS" not in env:
110 | env.update(MOZ_HEADLESS="1")
111 |
112 | if attempt >= 2 and prev_out and prev_out[0]:
113 | prev_rc_path = Path(prev_out[0]) / rc_name
114 | prev_rc = prev_rc_path.read_text(**UTF8).strip()
115 | if prev_rc == is_ok:
116 | rc_path.parent.mkdir(parents=True, exist_ok=True)
117 | rc_path.write_text(is_ok, **UTF8)
118 | print(f" ... skipping attempt {attempt} because previous attempt passed")
119 | return True
120 | print(f" .... previous rc {prev_rc}")
121 |
122 | args = [*conda_run]
123 |
124 | if dry_run:
125 | args += [
126 | "robot",
127 | "--dry-run",
128 | ]
129 | else:
130 | args += [
131 | # pabot
132 | "pabot",
133 | "--processes",
134 | os.environ["ATEST_PROCESSES"],
135 | "--artifactsinsubfolders",
136 | "--artifacts",
137 | "png,log,txt,svg,ipynb,json",
138 | ]
139 |
140 | args += [
141 | # robot
142 | f"--variable=ATTEMPT:{ attempt }",
143 | f"""--variable=OS:{ os.environ["THIS_SUBDIR"] }""",
144 | f"""--variable=PY:{ os.environ.get("JLF_PY", os.environ.get("THIS_PY")) }""",
145 | f"""--variable=LAB:{ os.environ["JLF_LAB"] }""",
146 | f"--variable=JSCOV:{jscov[0]}",
147 | "--variable=ROOT:../../..",
148 | "--outputdir",
149 | out_dir[0],
150 | *(atest_args or []),
151 | ]
152 |
153 | if attempt >= 2:
154 | args += [
155 | "--loglevel",
156 | "TRACE",
157 | "--rerunfailed",
158 | f"{prev_out[0]}/output.xml",
159 | ]
160 | args += atest_dir
161 |
162 | print(">>>", " ".join(args))
163 | rc = subprocess.call(args, env=env)
164 | print(f" ... returned {rc}")
165 |
166 | rc_path.write_text(f"{rc}", **UTF8)
167 |
168 | if rc:
169 | if dry_run or attempt == last_attempt:
170 | print(f" !!! FAILED after {last_attempt} attempts")
171 | return False
172 | print(
173 | f" !!! FAILED attempt {attempt}: {rc}, "
174 | f"run dt:atest:a_{last_attempt} (or dt:atest:a_*) for a real error code",
175 | )
176 |
177 | return True
178 |
179 |
180 | def copy_labextensions(prefix: str, *pkg_jsons: str) -> None:
181 | """Deploy already-built labextensions."""
182 | labextensions_root = Path(prefix) / "share/jupyter/labextensions"
183 | for pkg in pkg_jsons:
184 | pkg_path = Path(pkg)
185 | pkg_dir = pkg_path.parent
186 | print("... labextension:", pkg_dir)
187 | pkg_data = json.loads(pkg_path.read_text(**UTF8))
188 | pkg_name = f"""{pkg_data["name"]}"""
189 | dest = labextensions_root / pkg_name
190 | if dest.exists():
191 | shutil.rmtree(dest)
192 | if not dest.parent.exists():
193 | dest.parent.mkdir(parents=True)
194 | shutil.copytree(pkg_dir, dest)
195 | print(" ... copied to:", dest)
196 |
197 |
198 | def touch(*paths: str) -> None:
199 | """Ensure some paths exist (including parent folders)."""
200 | for path in map(Path, paths):
201 | if not path.parent.exists():
202 | path.parent.mkdir(parents=True)
203 | path.touch()
204 |
205 |
206 | def rebot(log_html, conda_run):
207 | """Merge robot reports.
208 |
209 | In the future:
210 | - fix relative paths
211 | - maybe run libdoc
212 | """
213 | cwd = Path(log_html[0]).parent
214 | log_root = cwd.parent
215 | if not log_root.is_dir():
216 | print(f"Can't even look for `output.xml` in missing {log_root}")
217 | return False
218 | shutil.rmtree(cwd, ignore_errors=True)
219 | cwd.mkdir()
220 | all_output = sorted(
221 | p
222 | for p in log_root.glob("*/output.xml")
223 | if not p.parent.name.endswith("a_0") or p.parent.name == "ALL"
224 | )
225 | if not all_output:
226 | print(f"No robot non dry-run `output.xml` files found in {log_root}")
227 | return False
228 | subprocess.call(
229 | [
230 | *conda_run,
231 | "rebot",
232 | "--processemptysuite",
233 | "--nostatusrc",
234 | *all_output,
235 | ],
236 | cwd=str(cwd),
237 | )
238 | return True
239 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ![fonts-icon] jupyterlab-fonts
2 |
3 | > Data-driven Style and Typography for [JupyterLab] powered by [JSS].
4 |
5 | [jupyterlab]: https://github.com/jupyterlab/jupyterlab
6 | [jss]: http://cssinjs.org
7 |
8 | [![ci-badge]][ci] [![demo-badge]][demo]
9 |
10 | [ci]:
11 | https://github.com/deathbeds/jupyterlab-fonts/actions?query=branch%3Amain
12 | 'current build status of jupyterlab-fonts'
13 | [ci-badge]:
14 | https://github.com/deathbeds/jupyterlab-fonts/actions/workflows/ci.yml/badge.svg
15 | [demo]:
16 | https://mybinder.org/v2/gh/deathbeds/jupyterlab-fonts/main?urlpath=lab
17 | 'an interactive demo of jupyterlab-fonts'
18 | [demo-badge]: https://mybinder.org/badge_logo.svg
19 |
20 | > ## This is **Free** Software
21 | >
22 | > We're trying some things out here, and invite you test it out, but make no guarantees
23 | > that it is good or even works. What we mean by that is covered in the shouty text at
24 | > the bottom of the [LICENSE].
25 | >
26 | > If something is broken, [become a contributor][contributing] and raise an [issue], but
27 | > we cannot guarantee any kind of response time. Similarly, [PR]s will be reviewed on a
28 | > time-permitting basis.
29 |
30 | [license]:
31 | https://github.com/deathbeds/jupyterlab-fonts/blob/main/LICENSE
32 | 'BSD-3-Clause'
33 | [contributing]:
34 | https://github.com/deathbeds/jupyterlab-fonts/blob/main/CONTRIBUTING.md
35 | 'contribute to jupyterlab-fonts'
36 | [changelog]:
37 | https://github.com/deathbeds/jupyterlab-fonts/blob/main/CHANGELOG.md
38 | 'the history of jupyterlab-fonts'
39 | [pr]:
40 | https://github.com/deathbeds/jupyterlab-fonts/pulls
41 | 'open pull requests to jupyterlab-fonts'
42 | [issue]:
43 | https://github.com/deathbeds/jupyterlab-fonts/issues
44 | 'open issues for jupyterlab-fonts'
45 |
46 | # Prerequisites
47 |
48 | - Python >=3.8
49 | - a Jupyter client
50 | - JupyterLab >=3,<5
51 | - _for specific JupyterLab compatibility, see the [changelog]._
52 | - Jupyter Notebook >=7,<8
53 |
54 | # Installing
55 |
56 | ```bash
57 | pip install jupyterlab-fonts
58 | # or
59 | conda install -c conda-forge jupyterlab-fonts
60 | ```
61 |
62 | # Uninstalling
63 |
64 | We're sorry to see you go!
65 |
66 | ```bash
67 | pip uninstall jupyterlab-fonts
68 | # or
69 | conda uninstall jupyterlab-fonts
70 | ```
71 |
72 | # Usage
73 |
74 | ## JupyterLab
75 |
76 | ### Quick Configuration with the Jupyter Lab Menu
77 |
78 | To change your default fonts, from the main menu, select _Settings_ ▶ _Fonts_ ▶ _Code_
79 | ▶ _Font_ (or _Size_ or _Line Height_) and the value you'd like.
80 |
81 | Some features of _Content_, i.e. your rendered Markdown and HTML, are also available,
82 | and more will hopefully be added over time.
83 |
84 | ### Full Configuration with the ![][fonts-icon]**Font Editor**
85 |
86 | You can view all available font configurations by selecting _Settings_ ▶ _Fonts_ ▶
87 | _Global Fonts..._. These values will be stored in your JupyterLab settings.
88 |
89 | ### Notebook-specific Configuration
90 |
91 | When viewing an `.ipynb`, change just the fonts for _that file_ by clicking
92 | ![fonts-icon] in the Notebook toolbar (right now, next to cell type). The font, style
93 | changes, and its license information will be stored in the Notebook metadata.
94 |
95 | > This can rapidly increase the size of your notebook file, and can make it harder to
96 | > use in collaboration. We're looking into some alterate approaches.
97 |
98 | [fonts-icon]:
99 | https://raw.githubusercontent.com/deathbeds/jupyterlab-fonts/main/packages/jupyterlab-fonts/style/icons/fonts.svg
100 |
101 | ### Advanced Configuration
102 |
103 | In JupyterLab, the _![fonts-icon] Fonts_ section of _Advanced JSON Settings_ can control
104 | things entirely unrelated to fonts. There's no guarantee that highly-customized styles
105 | will work nicely with the _Font Editor_, or with downstream applications of
106 | `jupyterlab-fonts` metadata.
107 |
108 | Here's an example of changing how a _Notebook_ file looks when in _Presentation Mode_.
109 |
110 | ```json
111 | {
112 | "styles": {
113 | ":root": {
114 | "--jp-code-font-family": "'Fira Code Regular', 'Source Code Pro', monospace",
115 | "--jp-code-font-size": "19px"
116 | },
117 | ".jp-mod-presentationMode .jp-Notebook": {
118 | "& .CodeMirror, & .cm-editor": {
119 | "fontSize": "32px"
120 | },
121 | "& .jp-InputPrompt, & .jp-OutputPrompt": {
122 | "display": "none"
123 | }
124 | }
125 | }
126 | }
127 | ```
128 |
129 | ### Notebooks
130 |
131 | Similarly, the JupyterLab _Property Inspector_ enables these customizations in a
132 | specific `.ipynb` file, at both the document and cell level: these are dynamically
133 | generated, and scoped to the document/cell `id`.
134 |
135 | ### Supporting Multiple Application Versions
136 |
137 | The above example shows how different versions of JupyterLab (or Notebook) may use
138 | different DOM classes for the same logical content, such as:
139 |
140 | | Element | JupyterLab <4 | JupyterLab 4 |
141 | | ------------- | ------------- | ------------ |
142 | | a code editor | `.CodeMirror` | `.cm-editor` |
143 |
144 | #### JSS Plugins
145 |
146 | All JSON-compatible features of the [`jss-preset-default` plugins][jss-plugins] are
147 | enabled with the default settings, with some specific notes below. For portability,
148 | dynamic JS-based features are not supported.
149 |
150 | ##### Nesting
151 |
152 | The the [`&` (ampersand)][jss-nesting] allows for nesting selectors, as standardized by
153 | the [W3C CSS Nesting Module][nesting-w3c] and implemented in [many
154 | browsers][nesting-browsers].
155 |
156 | ##### Global
157 |
158 | All settings-derived styles will be wrapped in a [`@global`][jss-global] selector.
159 |
160 | ### In Jupyter Workflows
161 |
162 | #### Use in `overrides.json`
163 |
164 | `overrides.json` allows for simple, declarative configuration of JupyterLab core and
165 | third-party extensions, even after the lab server has been started.
166 |
167 | ```json
168 | {
169 | "@deathbeds/jupyterlab-fonts:fonts": {
170 | "styles": {
171 | ":root": {
172 | "--jp-code-font-family": "'Fira Code Regular', 'Source Code Pro', monospace",
173 | "--jp-code-font-size": "19px"
174 | }
175 | }
176 | }
177 | }
178 | ```
179 |
180 | ##### Binder
181 |
182 | In [binder], one might deploy this with a `postBuild` script:
183 |
184 | ```bash
185 | #!/usr/bin/env bash
186 | set -eux
187 | mkdir -p "${NB_PYTHON_PREFIX}/share/jupyter/lab/settings"
188 | cp overrides.json "${NB_PYTHON_PREFIX}/share/jupyter/lab/settings"
189 | ```
190 |
191 | ##### JupyterLite
192 |
193 | Similarly, this is a well-known file to [JupyterLite][lite-well-known], making it
194 | straightforward to do light customization without needing to build and distribute a full
195 | theme [plugin][jupyterlab-plugins].
196 |
197 | [jupyterlab-plugins]:
198 | https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html#plugins
199 | [lite-well-known]:
200 | https://jupyterlite.readthedocs.io/en/latest/cli.html#well-known-files
201 | 'JupyterLite well known files'
202 | [binder]: https://mybinder.org
203 | [overrides-json]:
204 | https://jupyterlab.readthedocs.io/en/stable/user/directories.html#overrides-json
205 | 'JupyterLab settings overrides'
206 | [jss-plugins]: http://cssinjs.org/plugins#jss-plugins 'JSS plugins'
207 | [jss-nesting]:
208 | https://github.com/cssinjs/jss-nested#use--to-reference-selector-of-the-parent-rule
209 | 'using nested selectors in JSS'
210 | [jss-global]: https://cssinjs.org/jss-plugin-global 'the JSS global plugin'
211 | [nesting-browsers]: https://caniuse.com/css-nesting 'browsers that support & nesting'
212 | [nesting-w3c]: https://www.w3.org/TR/css-nesting-1 'the CSS nesting standard'
213 |
--------------------------------------------------------------------------------
/packages/jupyterlab-font-dejavu-sans-mono/vendor/dejavu-fonts-ttf/LICENSE:
--------------------------------------------------------------------------------
1 | Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
2 | Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
3 |
4 |
5 | Bitstream Vera Fonts Copyright
6 | ------------------------------
7 |
8 | Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
9 | a trademark of Bitstream, Inc.
10 |
11 | Permission is hereby granted, free of charge, to any person obtaining a copy
12 | of the fonts accompanying this license ("Fonts") and associated
13 | documentation files (the "Font Software"), to reproduce and distribute the
14 | Font Software, including without limitation the rights to use, copy, merge,
15 | publish, distribute, and/or sell copies of the Font Software, and to permit
16 | persons to whom the Font Software is furnished to do so, subject to the
17 | following conditions:
18 |
19 | The above copyright and trademark notices and this permission notice shall
20 | be included in all copies of one or more of the Font Software typefaces.
21 |
22 | The Font Software may be modified, altered, or added to, and in particular
23 | the designs of glyphs or characters in the Fonts may be modified and
24 | additional glyphs or characters may be added to the Fonts, only if the fonts
25 | are renamed to names not containing either the words "Bitstream" or the word
26 | "Vera".
27 |
28 | This License becomes null and void to the extent applicable to Fonts or Font
29 | Software that has been modified and is distributed under the "Bitstream
30 | Vera" names.
31 |
32 | The Font Software may be sold as part of a larger software package but no
33 | copy of one or more of the Font Software typefaces may be sold by itself.
34 |
35 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
36 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
37 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
38 | TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
39 | FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
40 | ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
41 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
42 | THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
43 | FONT SOFTWARE.
44 |
45 | Except as contained in this notice, the names of Gnome, the Gnome
46 | Foundation, and Bitstream Inc., shall not be used in advertising or
47 | otherwise to promote the sale, use or other dealings in this Font Software
48 | without prior written authorization from the Gnome Foundation or Bitstream
49 | Inc., respectively. For further information, contact: fonts at gnome dot
50 | org.
51 |
52 | Arev Fonts Copyright
53 | ------------------------------
54 |
55 | Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
56 |
57 | Permission is hereby granted, free of charge, to any person obtaining
58 | a copy of the fonts accompanying this license ("Fonts") and
59 | associated documentation files (the "Font Software"), to reproduce
60 | and distribute the modifications to the Bitstream Vera Font Software,
61 | including without limitation the rights to use, copy, merge, publish,
62 | distribute, and/or sell copies of the Font Software, and to permit
63 | persons to whom the Font Software is furnished to do so, subject to
64 | the following conditions:
65 |
66 | The above copyright and trademark notices and this permission notice
67 | shall be included in all copies of one or more of the Font Software
68 | typefaces.
69 |
70 | The Font Software may be modified, altered, or added to, and in
71 | particular the designs of glyphs or characters in the Fonts may be
72 | modified and additional glyphs or characters may be added to the
73 | Fonts, only if the fonts are renamed to names not containing either
74 | the words "Tavmjong Bah" or the word "Arev".
75 |
76 | This License becomes null and void to the extent applicable to Fonts
77 | or Font Software that has been modified and is distributed under the
78 | "Tavmjong Bah Arev" names.
79 |
80 | The Font Software may be sold as part of a larger software package but
81 | no copy of one or more of the Font Software typefaces may be sold by
82 | itself.
83 |
84 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
85 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
86 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
87 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
88 | TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
89 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
90 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
91 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
92 | OTHER DEALINGS IN THE FONT SOFTWARE.
93 |
94 | Except as contained in this notice, the name of Tavmjong Bah shall not
95 | be used in advertising or otherwise to promote the sale, use or other
96 | dealings in this Font Software without prior written authorization
97 | from Tavmjong Bah. For further information, contact: tavmjong @ free
98 | . fr.
99 |
100 | TeX Gyre DJV Math
101 | -----------------
102 | Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
103 |
104 | Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski
105 | (on behalf of TeX users groups) are in public domain.
106 |
107 | Letters imported from Euler Fraktur from AMSfonts are (c) American
108 | Mathematical Society (see below).
109 | Bitstream Vera Fonts Copyright
110 | Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera
111 | is a trademark of Bitstream, Inc.
112 |
113 | Permission is hereby granted, free of charge, to any person obtaining a copy
114 | of the fonts accompanying this license (“Fonts”) and associated
115 | documentation
116 | files (the “Font Software”), to reproduce and distribute the Font Software,
117 | including without limitation the rights to use, copy, merge, publish,
118 | distribute,
119 | and/or sell copies of the Font Software, and to permit persons to whom
120 | the Font Software is furnished to do so, subject to the following
121 | conditions:
122 |
123 | The above copyright and trademark notices and this permission notice
124 | shall be
125 | included in all copies of one or more of the Font Software typefaces.
126 |
127 | The Font Software may be modified, altered, or added to, and in particular
128 | the designs of glyphs or characters in the Fonts may be modified and
129 | additional
130 | glyphs or characters may be added to the Fonts, only if the fonts are
131 | renamed
132 | to names not containing either the words “Bitstream” or the word “Vera”.
133 |
134 | This License becomes null and void to the extent applicable to Fonts or
135 | Font Software
136 | that has been modified and is distributed under the “Bitstream Vera”
137 | names.
138 |
139 | The Font Software may be sold as part of a larger software package but
140 | no copy
141 | of one or more of the Font Software typefaces may be sold by itself.
142 |
143 | THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
144 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
145 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
146 | TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
147 | FOUNDATION
148 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL,
149 | SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN
150 | ACTION
151 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR
152 | INABILITY TO USE
153 | THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
154 | Except as contained in this notice, the names of GNOME, the GNOME
155 | Foundation,
156 | and Bitstream Inc., shall not be used in advertising or otherwise to promote
157 | the sale, use or other dealings in this Font Software without prior written
158 | authorization from the GNOME Foundation or Bitstream Inc., respectively.
159 | For further information, contact: fonts at gnome dot org.
160 |
161 | AMSFonts (v. 2.2) copyright
162 |
163 | The PostScript Type 1 implementation of the AMSFonts produced by and
164 | previously distributed by Blue Sky Research and Y&Y, Inc. are now freely
165 | available for general use. This has been accomplished through the
166 | cooperation
167 | of a consortium of scientific publishers with Blue Sky Research and Y&Y.
168 | Members of this consortium include:
169 |
170 | Elsevier Science IBM Corporation Society for Industrial and Applied
171 | Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS)
172 |
173 | In order to assure the authenticity of these fonts, copyright will be
174 | held by
175 | the American Mathematical Society. This is not meant to restrict in any way
176 | the legitimate use of the fonts, such as (but not limited to) electronic
177 | distribution of documents containing these fonts, inclusion of these fonts
178 | into other public domain or commercial font collections or computer
179 | applications, use of the outline data to create derivative fonts and/or
180 | faces, etc. However, the AMS does require that the AMS copyright notice be
181 | removed from any derivative versions of the fonts which have been altered in
182 | any way. In addition, to ensure the fidelity of TeX documents using Computer
183 | Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces,
184 | has requested that any alterations which yield different font metrics be
185 | given a different name.
186 |
187 | $Id$
188 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "jlpm build:schema && lerna run prebuild && jlpm build:ts",
5 | "build:schema": "lerna run build:schema",
6 | "build:static": "jlpm webpack -p",
7 | "build:ts": "cd packages && cd _meta && tsc -b",
8 | "clean": "jlpm clean:lib && jlpm clean:test",
9 | "clean:lib": "lerna exec --parallel -- rimraf lib",
10 | "clean:test": "rimraf _testoutput",
11 | "eslint:check": "eslint --cache --config=.eslintrc.js --ext \".js,.jsx,.ts,.tsx\" packages",
12 | "prettier:check": "prettier --list-different --cache --cache-location=build/.cache/prettier \"./*.{json,md,yml,js}\" \"{packages,.github,.binder}/**/*.{ts,tsx,css,json,md}\"",
13 | "test": "jlpm test:robot",
14 | "test:robot": "python -m robot -d _testoutput -X tests/acceptance",
15 | "watch": "cd packages/_meta && tsc -b -w",
16 | "watch:schema": "jlpm lerna run watch:schema --parallel"
17 | },
18 | "workspaces": [
19 | "packages/*"
20 | ],
21 | "resolutions": {
22 | "http-cache-semantics": "^4.1.1",
23 | "json5": "^2.2.3",
24 | "webpack": "^5.76.1"
25 | },
26 | "devDependencies": {
27 | "@ephesoft/webpack.istanbul.loader": "^2.2.0",
28 | "@istanbuljs/nyc-config-typescript": "^1.0.2",
29 | "@typescript-eslint/eslint-plugin": "^6.7.2",
30 | "@typescript-eslint/parser": "^6.7.2",
31 | "eslint": "^8.50.0",
32 | "eslint-config-prettier": "^9.0.0",
33 | "eslint-plugin-import": "^2.28.1",
34 | "eslint-plugin-prettier": "^5.0.0",
35 | "eslint-plugin-react": "^7.33.2",
36 | "file-loader": "^6.2.0",
37 | "lerna": "^7.3.0",
38 | "nyc": "^15.1.0",
39 | "prettier": "^3.0.3",
40 | "prettier-package-json": "^2.8.0",
41 | "raw-loader": "^4.0.2",
42 | "source-map-loader": "^4.0.1",
43 | "ts-node": "^10.9.1",
44 | "typescript": "~5.2.2",
45 | "webpack-bundle-analyzer": "^4.9.1",
46 | "yarn-berry-deduplicate": "^6.1.1"
47 | },
48 | "doitoml": {
49 | "prefix": "js-root",
50 | "paths": {
51 | "pj": [
52 | "package.json"
53 | ],
54 | "yarnrc": [
55 | ".yarnrc.yml"
56 | ],
57 | "all_pj": [
58 | "::pj",
59 | ":glob::packages::*/package.json"
60 | ],
61 | "yarn_history": [
62 | "node_modules/.yarn-state.yml"
63 | ],
64 | "all_md": [
65 | ":glob::.::*.md",
66 | ":rglob::.github::*.md",
67 | ":glob::packages::*/*.md"
68 | ],
69 | "all_empty_node_modules": [
70 | ":glob::packages::*/package.json::/s/::package.json::node_modules/.empty"
71 | ],
72 | "all_json": [
73 | ":glob::.::*.json",
74 | ":glob::packages::*/*.json",
75 | ":glob::packages::*/schema/*.json"
76 | ],
77 | "all_yml": [
78 | ":glob::.::*.yml",
79 | ":rglob::.github::*.yml"
80 | ],
81 | "all_ts": [
82 | ":glob::packages::*/src/**/*.ts",
83 | ":glob::packages::*/src/**/*.tsx"
84 | ],
85 | "all_js": [
86 | ":glob::.::*.js"
87 | ],
88 | "all_css": [
89 | ":glob::packages::*/style/**/*.css"
90 | ],
91 | "tsbuildinfo": [
92 | "packages/_meta/tsconfig.tsbuildinfo"
93 | ],
94 | "all_tsconfig": [
95 | ":glob::.::tsconfig*.json",
96 | ":glob::packages::*/tsconfig*.json"
97 | ],
98 | "labext_script": [
99 | "scripts/labextension.py"
100 | ]
101 | },
102 | "env": {
103 | "NX_CACHE_DIRECTORY": "${JLF_ROOT}/build/.cache/nx",
104 | "NX_PROJECT_GRAPH_CACHE_DIRECTORY": "${JLF_ROOT}/build/.cache/nx",
105 | "YARN_CACHE_FOLDER": "${JLF_ROOT}/build/.cache/yarn"
106 | },
107 | "tokens": {
108 | "jlpm": [
109 | "::dt::conda_run_build",
110 | "jlpm"
111 | ],
112 | "lerna": [
113 | "::jlpm",
114 | "lerna",
115 | "run",
116 | "--stream"
117 | ],
118 | "build_labext": [
119 | "::dt::conda_run_build",
120 | "python",
121 | "::labext_script",
122 | "build",
123 | "--debug"
124 | ]
125 | },
126 | "tasks": {
127 | "setup": {
128 | "file_dep": [
129 | "::yarnrc",
130 | "::dt::env_build_history",
131 | "::all_pj"
132 | ],
133 | "targets": [
134 | "::yarn_history"
135 | ],
136 | "actions": [
137 | {
138 | "py": {
139 | "scripts.actions:touch": {
140 | "args": [
141 | "::all_empty_node_modules"
142 | ]
143 | }
144 | }
145 | },
146 | [
147 | "::jlpm",
148 | "install"
149 | ],
150 | [
151 | "::jlpm",
152 | "yarn-berry-deduplicate",
153 | "--strategy=fewer",
154 | "--fail"
155 | ]
156 | ],
157 | "meta": {
158 | "doitoml": {
159 | "env": {
160 | "YARN_ENABLE_IMMUTABLE_INSTALLS": "false"
161 | }
162 | }
163 | }
164 | },
165 | "build": {
166 | "lib": {
167 | "actions": [
168 | [
169 | "::lerna",
170 | "build"
171 | ]
172 | ],
173 | "file_dep": [
174 | "::yarn_history",
175 | "::all_ts",
176 | "::all_tsconfig",
177 | "::*::prebuild_targets"
178 | ],
179 | "targets": [
180 | "::tsbuildinfo"
181 | ],
182 | "meta": {
183 | "doitoml": {
184 | "skip": "${WITH_JS_COV}"
185 | }
186 | }
187 | },
188 | "lib:cov": {
189 | "actions": [
190 | [
191 | "::lerna",
192 | "build:cov"
193 | ]
194 | ],
195 | "file_dep": [
196 | "::yarn_history",
197 | "::all_ts",
198 | "::all_tsconfig",
199 | "::*::prebuild_targets"
200 | ],
201 | "targets": [
202 | "::tsbuildinfo"
203 | ],
204 | "meta": {
205 | "doitoml": {
206 | "skip": {
207 | "not": "${WITH_JS_COV}"
208 | }
209 | }
210 | }
211 | }
212 | },
213 | "report": {
214 | "nyc": {
215 | "uptodate": [
216 | false
217 | ],
218 | "file_dep": [
219 | "::yarn_history"
220 | ],
221 | "actions": [
222 | [
223 | "::jlpm",
224 | "nyc",
225 | "report",
226 | "--report-dir",
227 | "::dt::nyc_html",
228 | "--temp-dir",
229 | "::dt::jscov"
230 | ]
231 | ]
232 | }
233 | },
234 | "watch": {
235 | "ts": {
236 | "uptodate": [
237 | false
238 | ],
239 | "actions": [
240 | [
241 | "::lerna",
242 | "--scope=@deathbeds/meta-jupyterlab-fonts",
243 | "watch"
244 | ]
245 | ],
246 | "file_dep": [
247 | "::yarn_history",
248 | "::all_ts",
249 | "::*::prebuild_targets"
250 | ]
251 | }
252 | },
253 | "fix": {
254 | "prettier-package-json": {
255 | "actions": [
256 | [
257 | "::jlpm",
258 | "prettier-package-json",
259 | "--write",
260 | "::all_pj"
261 | ]
262 | ],
263 | "file_dep": [
264 | "::all_pj",
265 | "::yarn_history"
266 | ]
267 | },
268 | "prettier": {
269 | "actions": [
270 | [
271 | "::jlpm",
272 | "prettier:check",
273 | "--write"
274 | ]
275 | ],
276 | "file_dep": [
277 | "::yarn_history",
278 | "::all_md",
279 | "::all_json",
280 | "::all_ts",
281 | "::all_css",
282 | "::all_yml",
283 | "::all_js"
284 | ]
285 | },
286 | "eslint": {
287 | "actions": [
288 | [
289 | "::jlpm",
290 | "eslint:check",
291 | "--fix"
292 | ]
293 | ],
294 | "file_dep": [
295 | "::yarn_history",
296 | "::all_ts",
297 | "::all_js"
298 | ]
299 | }
300 | },
301 | "lint": {
302 | "prettier": {
303 | "actions": [
304 | [
305 | "::jlpm",
306 | "prettier:check"
307 | ]
308 | ],
309 | "file_dep": [
310 | "::yarn_history",
311 | "::all_md",
312 | "::all_json",
313 | "::all_ts",
314 | "::all_css",
315 | "::all_yml",
316 | "::all_js"
317 | ]
318 | },
319 | "eslint": {
320 | "actions": [
321 | [
322 | "::jlpm",
323 | "eslint:check"
324 | ]
325 | ],
326 | "file_dep": [
327 | "::yarn_history",
328 | "::all_ts",
329 | "::all_js"
330 | ]
331 | }
332 | }
333 | }
334 | },
335 | "nyc": {
336 | "all": true,
337 | "extends": "@istanbuljs/nyc-config-typescript",
338 | "extension": [
339 | ".js",
340 | ".jsx",
341 | ".ts",
342 | ".tsx"
343 | ],
344 | "reporter": [
345 | "lcov",
346 | "html",
347 | "text",
348 | "text-summary"
349 | ],
350 | "require": [
351 | "ts-node/register",
352 | "source-map-support/register"
353 | ],
354 | "skip-full": true
355 | },
356 | "prettier": {
357 | "printWidth": 88,
358 | "proseWrap": "always",
359 | "semi": true,
360 | "singleQuote": true
361 | }
362 | }
363 |
--------------------------------------------------------------------------------
/packages/jupyterlab-fonts/src/editor.ts:
--------------------------------------------------------------------------------
1 | import { Dialog, showDialog, VDomModel, VDomRenderer } from '@jupyterlab/apputils';
2 | import { NotebookPanel } from '@jupyterlab/notebook';
3 | import { ReadonlyJSONObject } from '@lumino/coreutils';
4 | import * as React from 'react';
5 |
6 | import * as compat from './labcompat';
7 | import { FontManager } from './manager';
8 | import * as SCHEMA from './schema';
9 | import {
10 | TextKind,
11 | TEXT_OPTIONS,
12 | TEXT_LABELS,
13 | KIND_LABELS,
14 | TextProperty,
15 | IFontFaceOptions,
16 | PACKAGE_NAME,
17 | } from './tokens';
18 |
19 | import '../style/editor.css';
20 |
21 | const h = React.createElement;
22 |
23 | const EDITOR_CLASS = 'jp-FontsEditor';
24 | const ENABLED_CLASS = 'jp-FontsEditor-enable';
25 | const FIELD_CLASS = 'jp-FontsEditor-field';
26 | const EMBED_CLASS = 'jp-FontsEditor-embed';
27 | const SECTION_CLASS = 'lm-CommandPalette-header';
28 | const BUTTON_CLASS = 'jp-FontsEditor-button jp-mod-styled';
29 | const SIZE_CLASS = 'jp-FontsEditor-size';
30 | const DUMMY = '-';
31 |
32 | export class FontEditorModel extends VDomModel {
33 | private _notebook: NotebookPanel | null;
34 | private _fonts: FontManager;
35 |
36 | get fonts() {
37 | return this._fonts;
38 | }
39 |
40 | set fonts(fonts) {
41 | if (this._fonts && this._fonts.settings) {
42 | this._fonts.settings.changed.disconnect(this.onSettingsChange, this);
43 | }
44 | this._fonts = fonts;
45 | fonts.settings.changed.connect(this.onSettingsChange, this);
46 | this.stateChanged.emit(void 0);
47 | }
48 |
49 | private onSettingsChange() {
50 | this.stateChanged.emit(void 0);
51 | }
52 |
53 | get notebook() {
54 | return this._notebook;
55 | }
56 |
57 | set notebook(notebook) {
58 | if (this._notebook?.model) {
59 | compat
60 | .metadataSignal(this._notebook.model)
61 | .disconnect(this.onSettingsChange, this);
62 | this._notebook.context.pathChanged.disconnect(this.onSettingsChange, this);
63 | }
64 | this._notebook = notebook;
65 | if (this._notebook?.model) {
66 | compat.metadataSignal(this._notebook.model).connect(this.onSettingsChange, this);
67 | this._notebook.context.pathChanged.connect(this.onSettingsChange, this);
68 | }
69 | this.stateChanged.emit(void 0);
70 | }
71 |
72 | get enabled() {
73 | return this._fonts.enabled;
74 | }
75 |
76 | async setEnabled(enabled: boolean) {
77 | if (this.notebook == null) {
78 | await this._fonts.settings.set('enabled', enabled);
79 | this.stateChanged.emit(void 0);
80 | }
81 | }
82 |
83 | get notebookMetadata() {
84 | if (this.notebook?.model) {
85 | return compat.getPanelMetadata(
86 | this.notebook.model,
87 | PACKAGE_NAME,
88 | ) as SCHEMA.ISettings;
89 | }
90 | }
91 |
92 | clearNotebookMetadata(fontName?: string) {
93 | let meta = this.notebookMetadata;
94 | if (fontName) {
95 | if (meta?.fonts) {
96 | delete meta.fonts[fontName];
97 | }
98 | if (meta?.fontLicenses) {
99 | delete meta.fontLicenses[fontName];
100 | }
101 | }
102 | if (this.notebook?.model) {
103 | compat.setPanelMetadata(
104 | this.notebook.model,
105 | PACKAGE_NAME,
106 | JSON.parse(JSON.stringify(meta)) as any,
107 | );
108 | }
109 | this.stateChanged.emit(void 0);
110 | }
111 |
112 | dispose() {
113 | if (this._fonts && this._fonts.settings) {
114 | this._fonts.settings.changed.disconnect(this.onSettingsChange, this);
115 | }
116 | super.dispose();
117 | }
118 | }
119 |
120 | export class FontEditor extends VDomRenderer {
121 | constructor() {
122 | super(new FontEditorModel());
123 | this.addClass(EDITOR_CLASS);
124 | }
125 |
126 | protected render(): React.ReactElement {
127 | const m = this.model;
128 | if (!m) {
129 | return h('div', { key: 'empty' });
130 | }
131 |
132 | return h('div', { key: 'editor' }, [
133 | ...this.header(),
134 | ...[TextKind.code, TextKind.content].map((kind) =>
135 | h('section', { key: `${kind}-section`, title: KIND_LABELS[kind] }, [
136 | h(
137 | 'h3',
138 | { key: `${kind}-header`, className: SECTION_CLASS },
139 | KIND_LABELS[kind],
140 | ),
141 | ...['font-family', 'font-size', 'line-height'].map((prop: TextProperty) =>
142 | this.textSelect(prop, kind, { key: `${kind}-${prop}` }),
143 | ),
144 | ]),
145 | ),
146 | ...[TextKind.ui].map((kind) =>
147 | h('section', { key: `${kind}-section`, title: KIND_LABELS[kind] }, [
148 | h(
149 | 'h3',
150 | { key: `${kind}-header`, className: SECTION_CLASS },
151 | KIND_LABELS[kind],
152 | ),
153 | ...['font-family'].map((prop: TextProperty) =>
154 | this.textSelect(prop, kind, { key: `${kind}-${prop}` }),
155 | ),
156 | ]),
157 | ),
158 | ]);
159 | }
160 |
161 | protected fontFaceExtras(m: FontEditorModel, fontFamily: string) {
162 | let font: IFontFaceOptions | undefined;
163 | let unquoted = `${fontFamily}`.slice(1, -1);
164 | if (m.fonts.fonts.get(unquoted)) {
165 | font = m.fonts.fonts.get(unquoted);
166 | }
167 | return !font ? [] : [this.licenseButton(m, font)];
168 | }
169 |
170 | protected licenseButton(m: FontEditorModel, font: IFontFaceOptions) {
171 | return h(
172 | 'button',
173 | {
174 | className: BUTTON_CLASS,
175 | title: font.license.name,
176 | key: font.name,
177 | onClick: () => m.fonts.requestLicensePane(font),
178 | },
179 | font.license.spdx,
180 | );
181 | }
182 |
183 | protected textSelect(
184 | prop: TextProperty,
185 | kind: TextKind,
186 | sectionProps: ReadonlyJSONObject,
187 | ) {
188 | const m = this.model;
189 | const onChange = (evt: React.FormEvent) => {
190 | let value: string | null = (evt.target as HTMLSelectElement).value;
191 | value = value === DUMMY ? null : value;
192 | m.fonts
193 | .setTextStyle(prop, value, {
194 | kind,
195 | ...(m.notebook ? { notebook: m.notebook } : {}),
196 | })
197 | .catch(console.warn);
198 | };
199 | const value = m.fonts.getTextStyle(prop, {
200 | kind,
201 | notebook: m.notebook || void 0,
202 | });
203 | const extra = prop === 'font-family' ? this.fontFaceExtras(m, value as any) : [];
204 |
205 | return h('div', { className: FIELD_CLASS, key: 'select-field', ...sectionProps }, [
206 | h('label', { key: 'select-label' }, TEXT_LABELS[prop]),
207 | h('div', { key: 'select-wrap' }, [
208 | ...extra,
209 | h(
210 | 'select',
211 | {
212 | className: 'jp-mod-styled',
213 | title: `${TEXT_LABELS[prop]}`,
214 | onChange,
215 | defaultValue: value || DUMMY,
216 | key: `select`,
217 | },
218 | [null, ...TEXT_OPTIONS[prop](m.fonts)].map((value) => {
219 | return h(
220 | 'option',
221 | {
222 | key: `'${value}'`,
223 | value:
224 | value == null ? DUMMY : prop === 'font-family' ? `'${value}'` : value,
225 | },
226 | `${value || DUMMY}`,
227 | );
228 | }),
229 | ),
230 | ]),
231 | ]);
232 | }
233 |
234 | protected deleteButton(m: FontEditorModel, fontName: string) {
235 | return h(
236 | 'button',
237 | {
238 | className: BUTTON_CLASS,
239 | title: `Delete Embedded Font`,
240 | key: 'delete',
241 | onClick: async () => {
242 | const result = await showDialog({
243 | title: `Delete Embedded Font from Notebook`,
244 | body: `If you dont have ${fontName} installed, you might not be able to re-embed it`,
245 | buttons: [Dialog.cancelButton(), Dialog.warnButton({ label: 'DELETE' })],
246 | });
247 |
248 | if (result.button.accept) {
249 | m.clearNotebookMetadata(fontName);
250 | }
251 | },
252 | },
253 | 'Delete',
254 | );
255 | }
256 |
257 | protected enabler(m: FontEditorModel) {
258 | const onChange = async (evt: Event) => {
259 | await m.setEnabled(!!(evt.currentTarget as HTMLInputElement).checked);
260 | };
261 |
262 | return h(
263 | 'label',
264 | { key: 'enable-label' },
265 | h('span', { key: 'enable-text' }, 'Enabled'),
266 | h('input', {
267 | key: 'enable-input',
268 | type: 'checkbox',
269 | checked: m.enabled,
270 | onChange,
271 | }),
272 | );
273 | }
274 |
275 | protected embeddedFont(m: FontEditorModel, fontName: string) {
276 | if (m.notebookMetadata?.fonts == null || m.notebookMetadata.fontLicenses == null) {
277 | return null;
278 | }
279 | const faces = m.notebookMetadata.fonts[fontName];
280 | const license = m.notebookMetadata.fontLicenses[fontName];
281 | const size = (faces || []).reduce(
282 | (memo, face) => memo + `${face.src}`.length,
283 | license.text.length,
284 | );
285 | const kb = parseInt(`${size / 1024}`, 10);
286 |
287 | return h('li', { key: fontName }, [
288 | h('label', { key: 'label' }, fontName),
289 | this.licenseButton(m, {
290 | name: fontName,
291 | license: {
292 | name: license.name,
293 | spdx: license.spdx,
294 | text: async () => license.text,
295 | holders: license.holders,
296 | },
297 | faces: async () => faces || [],
298 | }),
299 | h('span', { className: SIZE_CLASS, key: 'font-kb' }, `${kb} kb`),
300 | this.deleteButton(m, fontName),
301 | ]);
302 | }
303 |
304 | protected header() {
305 | const m = this.model;
306 | const title = m.notebook
307 | ? m.notebook.context.contentsModel?.name.replace(/.ipynb$/, '')
308 | : 'Global';
309 |
310 | this.title.label = title || 'Unknown';
311 |
312 | const h2 = h('h2', { key: 'scope-head' }, [
313 | h('label', { key: 'scope-label' }, `Fonts » ${title}`),
314 | ...(m.notebook
315 | ? [h('div', { className: 'jp-NotebookIcon', key: 'scope-icon' })]
316 | : []),
317 | ]);
318 |
319 | if (m.notebook != null) {
320 | return [
321 | h2,
322 | h('section', { key: 'embed-section' }, [
323 | h('h3', { className: SECTION_CLASS, key: 'embed-head' }, 'Embedded fonts'),
324 | h(
325 | 'ul',
326 | { className: EMBED_CLASS, key: 'embeds' },
327 | Object.keys((m.notebookMetadata || {}).fonts || {}).map((fontName) => {
328 | return this.embeddedFont(m, fontName);
329 | }),
330 | ),
331 | ]),
332 | ];
333 | } else {
334 | return [
335 | h2,
336 | h('section', { key: 'enable-section', className: ENABLED_CLASS }, [
337 | h(
338 | 'h3',
339 | { className: SECTION_CLASS, key: 'enable-header' },
340 | 'Enable/Disable All Fonts',
341 | ),
342 | this.enabler(m),
343 | ]),
344 | ];
345 | }
346 | }
347 | }
348 |
--------------------------------------------------------------------------------