├── .projectignore ├── .github ├── specs │ ├── _run.yml │ ├── _py.yml │ ├── _node.yml │ ├── _py │ │ ├── py3.8.yml │ │ └── py3.11.yml │ ├── _lab.yml │ ├── _lab │ │ ├── lab3.5.yml │ │ └── lab4.0.yml │ ├── _base.yml │ ├── _build.yml │ ├── atest.yml │ ├── _atest.yml │ ├── _utest.yml │ ├── lab.yml │ ├── build.yml │ ├── lock.yml │ ├── _lint.yml │ ├── binder.yml │ ├── __lock__.toml │ └── __lock__.py ├── .condarc ├── ISSUE_TEMPLATE │ ├── feature_request.md │ ├── docs.md │ ├── release.md │ └── bug_report.md ├── locks │ ├── atest_osx-64.conda.lock │ ├── atest_win-64.conda.lock │ └── atest_linux-64.conda.lock └── workflows │ └── ci.yml ├── packages ├── jupyterlab-fonts │ ├── style │ │ ├── index.css │ │ ├── icons │ │ │ ├── delete-outline.svg │ │ │ ├── fonts.svg │ │ │ ├── delete-forever.svg │ │ │ └── copyright.svg │ │ ├── license.css │ │ └── editor.css │ ├── .gitignore │ ├── tsconfig.json │ ├── src │ │ ├── typings.d.ts │ │ ├── index.ts │ │ ├── schema.ts │ │ ├── icons.ts │ │ ├── button.ts │ │ ├── license.tsx │ │ ├── labcompat.ts │ │ ├── plugin.ts │ │ ├── util.ts │ │ ├── tokens.ts │ │ └── editor.ts │ ├── tsconfig.cov.json │ ├── README.md │ ├── webpack.config.js │ ├── LICENSE │ ├── package.json │ └── schema │ │ └── fonts.json ├── _meta │ ├── README.md │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── package.json ├── jupyterlab-font-fira-code │ ├── src │ │ ├── typings.d.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── tsconfig.cov.json │ ├── README.md │ ├── webpack.config.js │ ├── LICENSE │ └── package.json ├── jupyterlab-font-anonymous-pro │ ├── src │ │ ├── typings.d.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── tsconfig.cov.json │ ├── README.md │ ├── webpack.config.js │ ├── LICENSE │ ├── package.json │ └── vendor │ │ └── anonymous-pro │ │ └── LICENSE ├── jupyterlab-font-dejavu-sans-mono │ ├── src │ │ ├── typings.d.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── tsconfig.cov.json │ ├── README.md │ ├── scripts │ │ └── convert.py │ ├── webpack.config.js │ ├── LICENSE │ ├── package.json │ └── vendor │ │ └── dejavu-fonts-ttf │ │ └── LICENSE └── jupyterlab-font-atkinson-hyperlegible │ ├── src │ ├── typings.d.ts │ └── index.ts │ ├── tsconfig.json │ ├── tsconfig.cov.json │ ├── README.md │ ├── webpack.config.js │ ├── LICENSE │ └── package.json ├── setup.py ├── tests ├── __init__.py └── test_metadata.py ├── tsconfig.eslint.json ├── .eslint.tsconfig.json ├── lerna.json ├── CODE_OF_CONDUCT.md ├── .binder ├── postBuild └── environment.yml ├── .gitignore ├── atest ├── fixtures │ ├── jupyter_config.json │ └── overrides.json ├── __init__.robot ├── Global.robot ├── Notebook.robot ├── Menus.robot ├── _variables.resource └── Cells.robot ├── .eslintignore ├── src └── jupyterlab_fonts │ ├── __init__.py │ └── _version.py ├── .yarnrc.yml ├── tsconfigbase.json ├── scripts ├── labextension.py └── actions.py ├── LICENSE ├── CHANGELOG.md ├── CONTRIBUTING.md ├── .eslintrc.js ├── dodo.py ├── examples └── index.ipynb ├── README.md └── package.json /.projectignore: -------------------------------------------------------------------------------- 1 | *.ipynb 2 | -------------------------------------------------------------------------------- /.github/specs/_run.yml: -------------------------------------------------------------------------------- 1 | dependencies: [] 2 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/style/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | __import__("setuptools").setup() 2 | -------------------------------------------------------------------------------- /packages/_meta/README.md: -------------------------------------------------------------------------------- 1 | > nothing to see here 2 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/.gitignore: -------------------------------------------------------------------------------- 1 | src/**/_*.d.ts 2 | -------------------------------------------------------------------------------- /.github/specs/_py.yml: -------------------------------------------------------------------------------- 1 | _inherit_from: 2 | - _py/py3.11.yml 3 | -------------------------------------------------------------------------------- /.github/specs/_node.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - nodejs >=20,<21 3 | -------------------------------------------------------------------------------- /.github/specs/_py/py3.8.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - python ==3.8.* 3 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Tests for jupyterlab-fonts distribution.""" 2 | -------------------------------------------------------------------------------- /.github/specs/_py/py3.11.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - python ==3.11.* 3 | -------------------------------------------------------------------------------- /.github/specs/_lab.yml: -------------------------------------------------------------------------------- 1 | _inherit_from: 2 | - _lab/lab4.0.yml 3 | 4 | dependencies: [] 5 | -------------------------------------------------------------------------------- /.github/specs/_lab/lab3.5.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - python ==3.8.* 3 | - jupyterlab >=3.5,<3.6 4 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfigbase", 3 | "include": ["packages/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /.github/specs/_base.yml: -------------------------------------------------------------------------------- 1 | channels: 2 | - conda-forge 3 | - nodefaults 4 | dependencies: 5 | - pip 6 | -------------------------------------------------------------------------------- /.eslint.tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfigbase", 3 | "include": ["packages/*/src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /.github/specs/_build.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - brotlipy 3 | - flit >=3.9.0,<4 4 | - fonttools 5 | - twine >=3.7.1 6 | -------------------------------------------------------------------------------- /.github/specs/_lab/lab4.0.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - python ==3.11.* 3 | - jupyterlab >=4.0.7,<5.0.0a0 4 | - notebook >=7.0.6,<8.0.0a0 5 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 3 | "version": "independent", 4 | "npmClient": "yarn" 5 | } 6 | -------------------------------------------------------------------------------- /.github/specs/atest.yml: -------------------------------------------------------------------------------- 1 | # tests the packages in the browser 2 | 3 | _inherit_from: 4 | - _base.yml 5 | - _atest.yml 6 | 7 | dependencies: [] 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | > We adhere to the 4 | > [Jupyter Code Of Conduct](https://jupyter.org/governance/conduct/code_of_conduct.html) 5 | -------------------------------------------------------------------------------- /.github/specs/_atest.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - firefox >=115,<116 3 | - geckodriver 4 | - robotframework >=6.1 5 | - robotframework-jupyterlibrary >=0.5.0 6 | - robotframework-pabot 7 | -------------------------------------------------------------------------------- /.github/specs/_utest.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - coverage 3 | - pytest-asyncio 4 | - pytest-console-scripts 5 | - pytest-cov 6 | - pytest-html 7 | - pytest-json-report 8 | - pytest-xdist 9 | -------------------------------------------------------------------------------- /.github/specs/lab.yml: -------------------------------------------------------------------------------- 1 | # runs the packages for tests 2 | _matrix: 3 | - _lab 4 | 5 | _inherit_from: 6 | - _base.yml 7 | - _run.yml 8 | - _utest.yml 9 | 10 | dependencies: 11 | - ipywidgets 12 | -------------------------------------------------------------------------------- /.github/specs/build.yml: -------------------------------------------------------------------------------- 1 | # builds the various packages 2 | 3 | _inherit_from: 4 | - _base.yml 5 | - _node.yml 6 | - _build.yml 7 | - _lab/lab4.0.yml 8 | - _lint.yml 9 | 10 | dependencies: [] 11 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*", "src/@types/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const value: string; 3 | export default value; 4 | } 5 | 6 | declare module '*.woff2' { 7 | const content: string; 8 | export default content; 9 | } 10 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-fira-code/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '!!raw-loader!*' { 2 | const content: string; 3 | export default content; 4 | } 5 | declare module '*.woff2' { 6 | const content: string; 7 | export default content; 8 | } 9 | -------------------------------------------------------------------------------- /.github/specs/lock.yml: -------------------------------------------------------------------------------- 1 | # updates lockfiles 2 | 3 | channels: 4 | - conda-forge 5 | - nodefaults 6 | 7 | dependencies: 8 | - conda <22.11.1 9 | - conda-lock >=2.3,<2.4 10 | - doitoml-with-all 11 | - mamba 12 | - python-dotenv 13 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-anonymous-pro/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '!!raw-loader!*' { 2 | const content: string; 3 | export default content; 4 | } 5 | declare module '*.woff2' { 6 | const content: string; 7 | export default content; 8 | } 9 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-dejavu-sans-mono/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '!!raw-loader!*' { 2 | const content: string; 3 | export default content; 4 | } 5 | declare module '*.woff2' { 6 | const content: string; 7 | export default content; 8 | } 9 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-atkinson-hyperlegible/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '!!raw-loader!*' { 2 | const content: string; 3 | export default content; 4 | } 5 | declare module '*.woff2' { 6 | const content: string; 7 | export default content; 8 | } 9 | -------------------------------------------------------------------------------- /.binder/postBuild: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source activate "${NB_PYTHON_PREFIX}" 3 | 4 | export DEMO_IN_BINDER=1 5 | 6 | set -eux 7 | 8 | doit lock:preflight 9 | doit -n8 dt:binder || doist list && doit dt:binder 10 | git clean -dxf node_modules build 11 | -------------------------------------------------------------------------------- /packages/_meta/src/index.ts: -------------------------------------------------------------------------------- 1 | import '@deathbeds/jupyterlab-fonts'; 2 | import '@deathbeds/jupyterlab-font-anonymous-pro'; 3 | import '@deathbeds/jupyterlab-font-dejavu-sans-mono'; 4 | import '@deathbeds/jupyterlab-font-fira-code'; 5 | import '@deathbeds/jupyterlab-font-atkinson-hyperlegible'; 6 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/tsconfig.cov.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src", 6 | "inlineSourceMap": true, 7 | "sourceMap": false 8 | }, 9 | "include": ["src/**/*", "src/@types/**/*"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-fira-code/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"], 8 | "references": [ 9 | { 10 | "path": "../jupyterlab-fonts" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './button'; 2 | export * from './labcompat'; 3 | export * from './license'; 4 | export * from './manager'; 5 | export * from './plugin'; 6 | export * from './schema'; 7 | export * from './stylist'; 8 | export * from './tokens'; 9 | export * from './util'; 10 | -------------------------------------------------------------------------------- /.github/specs/_lint.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - black-jupyter 3 | - ruff >=0.1.0 4 | - mypy >=1.6 5 | - taplo 6 | - pytest 7 | - robotframework-robocop 8 | - robotframework-seleniumlibrary 9 | - robotframework-tidy 10 | - ssort 11 | - types-jsonschema 12 | - types-ujson 13 | - types-pyyaml 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | _testoutput/ 3 | .env 4 | .envs/ 5 | .eslintcache 6 | .ipynb_checkpoints/ 7 | .pabotsuitenames 8 | .venv/ 9 | *.doit* 10 | *.egg-info/ 11 | *.log 12 | *.tsbuildinfo 13 | *.woff2 14 | build/ 15 | dist/ 16 | lib/ 17 | node_modules/ 18 | packages/*/lib 19 | src/_d/ 20 | Untitled*.ipynb 21 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-anonymous-pro/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"], 8 | "references": [ 9 | { 10 | "path": "../jupyterlab-fonts" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-dejavu-sans-mono/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"], 8 | "references": [ 9 | { 10 | "path": "../jupyterlab-fonts" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /atest/fixtures/jupyter_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "LabApp": { 3 | "log_level": "DEBUG", 4 | "open_browser": false 5 | }, 6 | "ServerApp": { 7 | "tornado_settings": { 8 | "page_config_data": { 9 | "buildCheck": false, 10 | "buildAvailable": false 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-atkinson-hyperlegible/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"], 8 | "references": [ 9 | { 10 | "path": "../jupyterlab-fonts" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.github/specs/binder.yml: -------------------------------------------------------------------------------- 1 | # demos the package 2 | 3 | platforms: 4 | - linux-64 5 | 6 | _inherit_from: 7 | - _base.yml 8 | - _run.yml 9 | - _node.yml 10 | - _build.yml 11 | - _lab/lab4.0.yml 12 | - lock.yml 13 | 14 | _target: ../../.binder/environment.yml 15 | 16 | dependencies: 17 | - jupyterhub-singleuser 18 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/style/icons/delete-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /atest/fixtures/overrides.json: -------------------------------------------------------------------------------- 1 | { 2 | "@jupyterlab/apputils-extension:notification": { 3 | "checkForUpdates": false, 4 | "doNotDisturbMode": true, 5 | "fetchNews": "false" 6 | }, 7 | "@jupyterlab/apputils-extension:palette": { 8 | "modal": false 9 | }, 10 | "@jupyterlab/extensionmanager-extension:plugin": { 11 | "enabled": false 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-fira-code/tsconfig.cov.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src", 6 | "inlineSourceMap": true, 7 | "sourceMap": false 8 | }, 9 | "include": ["src/**/*"], 10 | "references": [ 11 | { 12 | "path": "../jupyterlab-fonts" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-anonymous-pro/tsconfig.cov.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src", 6 | "inlineSourceMap": true, 7 | "sourceMap": false 8 | }, 9 | "include": ["src/**/*"], 10 | "references": [ 11 | { 12 | "path": "../jupyterlab-fonts" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/src/schema.ts: -------------------------------------------------------------------------------- 1 | import * as _ from './_schema'; 2 | 3 | export interface ISettings extends _.Fonts {} 4 | export interface IStyles extends _.GlobalStyles {} 5 | export type ICSSOM = _.ICSSOM; 6 | export type IFontFacePrimitive = _.IFontFacePrimitive; 7 | export type IFontFaceObject = _.EmbeddedFonts; 8 | export type IFontLicenseObject = _.EmbeddedFontLicenses; 9 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-dejavu-sans-mono/tsconfig.cov.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src", 6 | "inlineSourceMap": true, 7 | "sourceMap": false 8 | }, 9 | "include": ["src/**/*"], 10 | "references": [ 11 | { 12 | "path": "../jupyterlab-fonts" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | _*.d.ts 3 | ./lab/ 4 | .conda-packages/ 5 | .vscode/ 6 | .yarn-packages/ 7 | *.bundle.js 8 | *.db 9 | *.egg-info 10 | *.map.js 11 | **/build 12 | **/dist 13 | **/envs 14 | **/labextension 15 | **/lib 16 | **/mock_packages 17 | **/node_modules 18 | **/schemas 19 | **/static 20 | **/themes 21 | **/typings 22 | atest/ 23 | coverage 24 | docs/ 25 | node_modules 26 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-atkinson-hyperlegible/tsconfig.cov.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src", 6 | "inlineSourceMap": true, 7 | "sourceMap": false 8 | }, 9 | "include": ["src/**/*"], 10 | "references": [ 11 | { 12 | "path": "../jupyterlab-fonts" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/README.md: -------------------------------------------------------------------------------- 1 | # jupyterlab-fonts 2 | 3 | Data-driven Style and Typography for JupyterLab. 4 | 5 | Install this (and all the fonts) with: 6 | 7 | ```bash 8 | pip install jupyterlab-fonts 9 | # or 10 | conda install -c conda-forge jupyterlab-fonts 11 | ``` 12 | 13 | For more information, see the 14 | [contribution guide](https://github.com/deathbeds/jupyterlab-fonts/blob/main/CONTRIBUTING.md). 15 | -------------------------------------------------------------------------------- /.binder/environment.yml: -------------------------------------------------------------------------------- 1 | channels: 2 | - conda-forge 3 | - nodefaults 4 | dependencies: 5 | - brotlipy 6 | - conda <22.11.1 7 | - conda-lock >=2.3,<2.4 8 | - doitoml-with-all 9 | - flit >=3.9.0,<4 10 | - fonttools 11 | - jupyterhub-singleuser 12 | - jupyterlab >=4.0.7,<5.0.0a0 13 | - mamba 14 | - nodejs >=20,<21 15 | - notebook >=7.0.6,<8.0.0a0 16 | - pip 17 | - python ==3.11.* 18 | - python-dotenv 19 | - twine >=3.7.1 20 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/src/icons.ts: -------------------------------------------------------------------------------- 1 | import { LabIcon } from '@jupyterlab/ui-components'; 2 | 3 | import license from '!!raw-loader!../style/icons/copyright.svg'; 4 | import fonts from '!!raw-loader!../style/icons/fonts.svg'; 5 | 6 | export const ICONS = { 7 | fonts: new LabIcon({ 8 | name: 'fonts:fonts', 9 | svgstr: fonts, 10 | }), 11 | license: new LabIcon({ 12 | name: 'fonts:license', 13 | svgstr: license, 14 | }), 15 | }; 16 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/style/icons/fonts.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Aa 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-anonymous-pro/README.md: -------------------------------------------------------------------------------- 1 | # @deathbeds/jupyterlab-font-fira-code 2 | 3 | Provides four weights of [Fira Code](https://github.com/tonsky/FiraCode). 4 | 5 | Install this (and all the other fonts) with: 6 | 7 | ```bash 8 | pip install jupyterlab-fonts 9 | # or 10 | conda install -c conda-forge jupyterlab-fonts 11 | ``` 12 | 13 | For more information, see the 14 | [contribution guide](https://github.com/deathbeds/jupyterlab-fonts/blob/main/CONTRIBUTING.md). 15 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-fira-code/README.md: -------------------------------------------------------------------------------- 1 | # @deathbeds/jupyterlab-font-fira-code 2 | 3 | Provides four weights of [Fira Code](https://github.com/tonsky/FiraCode). 4 | 5 | Install this (and all the other fonts) with: 6 | 7 | ```bash 8 | pip install jupyterlab-fonts 9 | # or 10 | conda install -c conda-forge jupyterlab-fonts 11 | ``` 12 | 13 | For more information, see the 14 | [contribution guide](https://github.com/deathbeds/jupyterlab-fonts/blob/main/CONTRIBUTING.md). 15 | -------------------------------------------------------------------------------- /.github/.condarc: -------------------------------------------------------------------------------- 1 | add_pip_as_python_dependency: False 2 | aggressive_update_packages: [] 3 | always_yes: True 4 | auto_update_conda: False 5 | local_repodata_ttl: 99999 6 | notify_outdated_conda: False 7 | remote_connect_timeout_secs: 600 8 | remote_max_retries: 10 9 | remote_read_timeout_secs: 600 10 | show_channel_urls: True 11 | show_sources: True 12 | unsatisfiable_hints_check_depth: 0 13 | use_only_tar_bz2: True 14 | repodata_fns: 15 | - repodata.json.zst 16 | - repodata.json 17 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-dejavu-sans-mono/README.md: -------------------------------------------------------------------------------- 1 | # @deathbeds/jupyterlab-font-dejavu-sans-mono 2 | 3 | Provides two weights of [Dejavu](https://dejavu-fonts.github.io/). 4 | 5 | Install this (and all the other fonts) with: 6 | 7 | ```bash 8 | pip install jupyterlab-fonts 9 | # or 10 | conda install -c conda-forge jupyterlab-fonts 11 | ``` 12 | 13 | For more information, see the 14 | [contribution guide](https://github.com/deathbeds/jupyterlab-fonts/blob/main/CONTRIBUTING.md). 15 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/style/icons/delete-forever.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/jupyterlab_fonts/__init__.py: -------------------------------------------------------------------------------- 1 | """"main importable for jupyterlab-fonts.""" 2 | from ._version import __pkg__, __prefix__, __version__ 3 | 4 | 5 | def _jupyter_labextension_paths(): 6 | return [ 7 | { 8 | "src": str(pkg.parent.relative_to(__prefix__).as_posix()), 9 | "dest": pkg_name, 10 | } 11 | for pkg_name, pkg in __pkg__.items() 12 | ] 13 | 14 | 15 | __all__ = [ 16 | "__version__", 17 | "_jupyter_labextension_paths", 18 | ] 19 | -------------------------------------------------------------------------------- /tests/test_metadata.py: -------------------------------------------------------------------------------- 1 | """jupyterlab-fonts metadata tests.""" 2 | import jupyterlab_fonts 3 | 4 | EXPECTED_EXT_COUNT = 5 5 | 6 | 7 | def test_version(): 8 | """It has a version.""" 9 | assert jupyterlab_fonts.__version__, "no version" 10 | 11 | 12 | def test_magic_lab_extensions(): 13 | """It has the expected number of labextensions.""" 14 | assert ( 15 | len(jupyterlab_fonts._jupyter_labextension_paths()) == EXPECTED_EXT_COUNT 16 | ), "too many/few labextensions" 17 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-atkinson-hyperlegible/README.md: -------------------------------------------------------------------------------- 1 | # @deathbeds/jupyterlab-font-atkinson-hyperlegible 2 | 3 | Provides four weights of [Atkinson Hyperlegible]https://brailleinstitute.org/freefont). 4 | 5 | Install this (and all the other fonts) with: 6 | 7 | ```bash 8 | pip install jupyterlab-fonts 9 | # or 10 | conda install -c conda-forge jupyterlab-fonts 11 | ``` 12 | 13 | For more information, see the 14 | [contribution guide](https://github.com/deathbeds/jupyterlab-fonts/blob/main/CONTRIBUTING.md). 15 | -------------------------------------------------------------------------------- /packages/_meta/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"], 8 | "references": [ 9 | { 10 | "path": "../jupyterlab-font-anonymous-pro" 11 | }, 12 | { 13 | "path": "../jupyterlab-font-atkinson-hyperlegible" 14 | }, 15 | { 16 | "path": "../jupyterlab-fonts" 17 | }, 18 | { 19 | "path": "../jupyterlab-font-dejavu-sans-mono" 20 | }, 21 | { 22 | "path": "../jupyterlab-font-fira-code" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/jupyterlab_fonts/_version.py: -------------------------------------------------------------------------------- 1 | """Single source of truth for jupyterlab_fonts version.""" 2 | import sys 3 | from importlib.metadata import version 4 | from pathlib import Path 5 | 6 | HERE = Path(__file__).parent 7 | _D = HERE.parent / "_d" 8 | 9 | __prefix__ = _D if _D.exists() and _D.parent.name == "src" else Path(sys.prefix) 10 | 11 | __pkg__ = { 12 | f"{p.parent.parent.name}/{p.parent.name}": p 13 | for p in __prefix__.glob( 14 | "share/jupyter/labextensions/@deathbeds/jupyterlab-font*/package.json", 15 | ) 16 | } 17 | 18 | __version__ = version("jupyterlab_fonts") 19 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | enableInlineBuilds: false 2 | enableTelemetry: false 3 | httpTimeout: 60000 4 | nodeLinker: node-modules 5 | npmRegistryServer: https://registry.npmjs.org/ 6 | installStatePath: ./build/.cache/yarn/install-state.gz 7 | cacheFolder: ./build/.cache/yarn/cache 8 | logFilters: 9 | - code: YN0006 10 | level: discard 11 | - code: YN0002 12 | level: discard 13 | - code: YN0007 14 | level: discard 15 | - code: YN0013 16 | level: discard 17 | - code: YN0019 18 | level: discard 19 | - code: YN0008 20 | level: discard 21 | - code: YN0032 22 | level: discard 23 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/style/license.css: -------------------------------------------------------------------------------- 1 | .jp-LicenseViewer-wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .jp-LicenseViewer { 7 | flex: 1; 8 | display: flex; 9 | flex-direction: column; 10 | padding: var(--jp-notebook-padding); 11 | color: var(--jp-ui-font-color0); 12 | height: 100%; 13 | } 14 | 15 | .jp-LicenseViewer h1, 16 | .jp-LicenseViewer h2 { 17 | flex: 0; 18 | margin: var(--jp-notebook-padding) 0; 19 | } 20 | 21 | .jp-LicenseViewer h2 { 22 | color: var(--jp-ui-font-color1); 23 | } 24 | 25 | .jp-LicenseViewer pre { 26 | flex: 1; 27 | overflow-y: auto; 28 | font-size: 80%; 29 | } 30 | -------------------------------------------------------------------------------- /tsconfigbase.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "composite": true, 6 | "declaration": true, 7 | "esModuleInterop": true, 8 | "incremental": true, 9 | "jsx": "react", 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "noEmitOnError": true, 13 | "noImplicitAny": true, 14 | "noUnusedLocals": true, 15 | "preserveWatchOutput": true, 16 | "resolveJsonModule": true, 17 | "skipLibCheck": true, 18 | "strictNullChecks": true, 19 | "target": "es2017", 20 | "types": [] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/_meta/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@deathbeds/meta-jupyterlab-fonts", 4 | "version": "3.0.1", 5 | "scripts": { 6 | "build": "tsc -b --sourceMap", 7 | "build:cov": "tsc -b --inlineSourceMap", 8 | "watch": "jlpm build --watch --preserveWatchOutput" 9 | }, 10 | "devDependencies": { 11 | "@deathbeds/jupyterlab-font-anonymous-pro": "workspace:*", 12 | "@deathbeds/jupyterlab-font-atkinson-hyperlegible": "workspace:*", 13 | "@deathbeds/jupyterlab-font-dejavu-sans-mono": "workspace:*", 14 | "@deathbeds/jupyterlab-font-fira-code": "workspace:*", 15 | "@deathbeds/jupyterlab-fonts": "workspace:*" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Request a Future Roadmap item 3 | about: Help us build future features 4 | labels: enhancement 5 | --- 6 | 7 | 13 | 14 | ## Elevator Pitch 15 | 16 | 17 | 18 | ## Motivation 19 | 20 | 21 | 22 | ## Design Ideas 23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-dejavu-sans-mono/scripts/convert.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | from pathlib import Path 3 | from fontTools.ttLib import TTFont 4 | 5 | in_path = Path("../../node_modules/dejavu-fonts-ttf/ttf") 6 | out_path = Path("style") / "fonts" 7 | 8 | out_path.mkdir(exist_ok=True, parents=True) 9 | 10 | base = "DejaVuSansMono" 11 | extension = "ttf" 12 | variants = ["", "-Bold"] 13 | 14 | 15 | for variant in variants: 16 | ttf = in_path / f"{base}{variant}.{extension}" 17 | woff2_path = out_path / ttf.with_suffix(".woff2").name 18 | 19 | if woff2_path.exists(): 20 | continue 21 | 22 | font = TTFont(str(ttf)) 23 | font.flavor = "woff2" 24 | font.save(str(out_path / ttf.with_suffix(".woff2").name)) 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/docs.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation request 3 | about: Ask for clarification in documentation 4 | labels: documentation 5 | --- 6 | 7 | 13 | 14 | ## What I am trying to do... 15 | 16 | 17 | 18 | ## How I would like to learn how to do it... 19 | 20 | 21 | 22 | ## How the project might keep the docs accurate... 23 | 24 | 25 | -------------------------------------------------------------------------------- /scripts/labextension.py: -------------------------------------------------------------------------------- 1 | """A custom `jupyter-labextension` to enable the semi-weird directory layout.""" 2 | import importlib 3 | import sys 4 | from pathlib import Path 5 | 6 | from jupyterlab import federated_labextensions 7 | from jupyterlab.labextensions import LabExtensionApp 8 | 9 | HERE = Path(__file__).parent 10 | ROOT = HERE.parent 11 | NODE_MODULES = ROOT / "node_modules" 12 | BUILDER = NODE_MODULES / "@jupyterlab/builder/lib/build-labextension.js" 13 | 14 | 15 | def _get_labextension_metadata(module): 16 | m = importlib.import_module(module) 17 | return m, m._jupyter_labextension_paths() 18 | 19 | 20 | federated_labextensions._get_labextension_metadata = _get_labextension_metadata 21 | federated_labextensions._ensure_builder = lambda *_: str(BUILDER) 22 | 23 | main = LabExtensionApp.launch_instance 24 | 25 | if __name__ == "__main__": 26 | sys.exit(main()) 27 | -------------------------------------------------------------------------------- /atest/__init__.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Test interactive typography in JupyterLab 3 | 4 | Library OperatingSystem 5 | Library JupyterLibrary 6 | Library uuid 7 | Resource ./_keywords.resource 8 | 9 | Suite Setup Prepare for testing fonts 10 | Suite Teardown Clean up after testing fonts 11 | 12 | Test Tags py:${py} os:${os} attempt:${attempt} 13 | 14 | 15 | *** Variables *** 16 | ${LOG_DIR} ${OUTPUT_DIR}${/}logs 17 | 18 | 19 | *** Keywords *** 20 | Prepare for testing fonts 21 | ${home_dir} = Initialize Fake Home 22 | Initialize Jupyter Server ${home_dir} 23 | ${executable_path} = Get GeckoDriver 24 | Open JupyterLab executable_path=${executable_path} 25 | Set Window Size 1920 1080 26 | 27 | Clean up after testing fonts 28 | Terminate All Jupyter Servers 29 | Close All Browsers 30 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-dejavu-sans-mono/src/index.ts: -------------------------------------------------------------------------------- 1 | import { makePlugin } from '@deathbeds/jupyterlab-fonts'; 2 | 3 | const plugin = makePlugin({ 4 | id: '@deathbeds/jupyterlab-font-dejavu-sans-mono', 5 | fontName: `DejaVu Sans Mono`, 6 | license: { 7 | spdx: 'OTHER', 8 | name: 'DejaVu Font License', 9 | holders: [ 10 | `Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved.`, 11 | `Copyright (c) 2006 by Tavmjong Bah.`, 12 | ], 13 | }, 14 | licenseText: async () => { 15 | return (await import('!!raw-loader!../vendor/dejavu-fonts-ttf/LICENSE')).default; 16 | }, 17 | variants: async () => { 18 | return { 19 | '': [ 20 | { 21 | woff2: () => import(`!!file-loader!../style/fonts/DejaVuSansMono.woff2`), 22 | }, 23 | ], 24 | Bold: [ 25 | { 26 | woff2: () => import(`!!file-loader!../style/fonts/DejaVuSansMono-Bold.woff2`), 27 | }, 28 | ], 29 | }; 30 | }, 31 | }); 32 | 33 | export default plugin; 34 | -------------------------------------------------------------------------------- /atest/Global.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation The font editor allows changing fonts in notebooks 3 | 4 | Resource ./_keywords.resource 5 | 6 | Test Tags app:lab 7 | 8 | 9 | *** Test Cases *** 10 | Global Font Editor 11 | [Documentation] Customize Global fonts with the Font Editor 12 | [Template] Use the font editor to configure fonts 13 | [Setup] Open the Global Font Editor 14 | Global Code Anonymous Pro Bold 15 | Global Code Anonymous Pro Regular 16 | Global Code DejaVu Sans Mono 17 | Global Code DejaVu Sans Mono Bold 18 | Global Code Fira Code Bold 19 | Global Code Fira Code Light 20 | Global Code Fira Code Medium 21 | Global Code Fira Code Regular 22 | Global Content Atkinson Hyperlegible Regular 23 | Global Content Atkinson Hyperlegible Bold 24 | Global UI Atkinson Hyperlegible Regular 25 | Global UI Atkinson Hyperlegible Bold 26 | [Teardown] Close the Font Editor Global 27 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/webpack.config.js: -------------------------------------------------------------------------------- 1 | let rules = []; 2 | let plugins = []; 3 | 4 | const WITH_JS_COV = !!JSON.parse((process.env.WITH_JS_COV || '').toLowerCase()); 5 | const WITH_JS_VIZ = !!JSON.parse((process.env.WITH_JS_VIZ || '').toLowerCase()); 6 | 7 | if (WITH_JS_COV) { 8 | console.error('Building with coverage'); 9 | rules.push({ test: /\.js$/, use: ['@ephesoft/webpack.istanbul.loader'] }); 10 | } 11 | 12 | if (WITH_JS_VIZ) { 13 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); 14 | const path = require('path'); 15 | const pkg = path.basename(__dirname); 16 | const reportFilename = path.resolve(__dirname, `../../build/reports/webpack/${pkg}.html`); 17 | console.error('Building with visualized bundle', reportFilename); 18 | plugins.push( 19 | new BundleAnalyzerPlugin({ 20 | analyzerMode: 'static', 21 | reportFilename, 22 | openAnalyzer: false, 23 | }), 24 | ); 25 | } 26 | 27 | module.exports = { 28 | output: { clean: true }, 29 | devtool: 'source-map', 30 | module: { rules }, 31 | plugins, 32 | }; 33 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-anonymous-pro/webpack.config.js: -------------------------------------------------------------------------------- 1 | let rules = []; 2 | let plugins = []; 3 | 4 | const WITH_JS_COV = !!JSON.parse((process.env.WITH_JS_COV || '').toLowerCase()); 5 | const WITH_JS_VIZ = !!JSON.parse((process.env.WITH_JS_VIZ || '').toLowerCase()); 6 | 7 | if (WITH_JS_COV) { 8 | console.error('Building with coverage'); 9 | rules.push({ test: /\.js$/, use: ['@ephesoft/webpack.istanbul.loader'] }); 10 | } 11 | 12 | if (WITH_JS_VIZ) { 13 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); 14 | const path = require('path'); 15 | const pkg = path.basename(__dirname); 16 | const reportFilename = path.resolve(__dirname, `../../build/reports/webpack/${pkg}.html`); 17 | console.error('Building with visualized bundle', reportFilename); 18 | plugins.push( 19 | new BundleAnalyzerPlugin({ 20 | analyzerMode: 'static', 21 | reportFilename, 22 | openAnalyzer: false, 23 | }), 24 | ); 25 | } 26 | 27 | module.exports = { 28 | output: { clean: true }, 29 | devtool: 'source-map', 30 | module: { rules }, 31 | plugins, 32 | }; 33 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-fira-code/webpack.config.js: -------------------------------------------------------------------------------- 1 | let rules = []; 2 | let plugins = []; 3 | 4 | const WITH_JS_COV = !!JSON.parse((process.env.WITH_JS_COV || '').toLowerCase()); 5 | const WITH_JS_VIZ = !!JSON.parse((process.env.WITH_JS_VIZ || '').toLowerCase()); 6 | 7 | if (WITH_JS_COV) { 8 | console.error('Building with coverage'); 9 | rules.push({ test: /\.js$/, use: ['@ephesoft/webpack.istanbul.loader'] }); 10 | } 11 | 12 | if (WITH_JS_VIZ) { 13 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); 14 | const path = require('path'); 15 | const pkg = path.basename(__dirname); 16 | const reportFilename = path.resolve(__dirname, `../../build/reports/webpack/${pkg}.html`); 17 | console.error('Building with visualized bundle', reportFilename); 18 | plugins.push( 19 | new BundleAnalyzerPlugin({ 20 | analyzerMode: 'static', 21 | reportFilename, 22 | openAnalyzer: false, 23 | }), 24 | ); 25 | } 26 | 27 | module.exports = { 28 | output: { clean: true }, 29 | devtool: 'source-map', 30 | module: { rules }, 31 | plugins, 32 | }; 33 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-dejavu-sans-mono/webpack.config.js: -------------------------------------------------------------------------------- 1 | let rules = []; 2 | let plugins = []; 3 | 4 | const WITH_JS_COV = !!JSON.parse((process.env.WITH_JS_COV || '').toLowerCase()); 5 | const WITH_JS_VIZ = !!JSON.parse((process.env.WITH_JS_VIZ || '').toLowerCase()); 6 | 7 | if (WITH_JS_COV) { 8 | console.error('Building with coverage'); 9 | rules.push({ test: /\.js$/, use: ['@ephesoft/webpack.istanbul.loader'] }); 10 | } 11 | 12 | if (WITH_JS_VIZ) { 13 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); 14 | const path = require('path'); 15 | const pkg = path.basename(__dirname); 16 | const reportFilename = path.resolve(__dirname, `../../build/reports/webpack/${pkg}.html`); 17 | console.error('Building with visualized bundle', reportFilename); 18 | plugins.push( 19 | new BundleAnalyzerPlugin({ 20 | analyzerMode: 'static', 21 | reportFilename, 22 | openAnalyzer: false, 23 | }), 24 | ); 25 | } 26 | 27 | module.exports = { 28 | output: { clean: true }, 29 | devtool: 'source-map', 30 | module: { rules }, 31 | plugins, 32 | }; 33 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-atkinson-hyperlegible/webpack.config.js: -------------------------------------------------------------------------------- 1 | let rules = []; 2 | let plugins = []; 3 | 4 | const WITH_JS_COV = !!JSON.parse((process.env.WITH_JS_COV || '').toLowerCase()); 5 | const WITH_JS_VIZ = !!JSON.parse((process.env.WITH_JS_VIZ || '').toLowerCase()); 6 | 7 | if (WITH_JS_COV) { 8 | console.error('Building with coverage'); 9 | rules.push({ test: /\.js$/, use: ['@ephesoft/webpack.istanbul.loader'] }); 10 | } 11 | 12 | if (WITH_JS_VIZ) { 13 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); 14 | const path = require('path'); 15 | const pkg = path.basename(__dirname); 16 | const reportFilename = path.resolve(__dirname, `../../build/reports/webpack/${pkg}.html`); 17 | console.error('Building with visualized bundle', reportFilename); 18 | plugins.push( 19 | new BundleAnalyzerPlugin({ 20 | analyzerMode: 'static', 21 | reportFilename, 22 | openAnalyzer: false, 23 | }), 24 | ); 25 | } 26 | 27 | module.exports = { 28 | output: { clean: true }, 29 | devtool: 'source-map', 30 | module: { rules }, 31 | plugins, 32 | }; 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/release.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | about: Prepare for a release 4 | labels: maintenance 5 | --- 6 | 7 | - [ ] merge all outstanding PRs 8 | - [ ] ensure the versions have been bumped (check with `doit`) 9 | - [ ] ensure the CHANGELOG is up-to-date 10 | - [ ] move the new release to the top of the stack 11 | - [ ] validate on binder 12 | - [ ] wait for a successful build of `main` 13 | - [ ] download the `dist` archive and unpack somewhere (maybe a fresh `dist`) 14 | - [ ] create a new release through the GitHub UI 15 | - [ ] paste in the relevant CHANGELOG entries 16 | - [ ] upload the artifacts 17 | - [ ] actually upload to npm.com, pypi.org 18 | ```bash 19 | #!/usr/bin/env bash 20 | set -eux 21 | cd dist 22 | twine upload *.tar.gz *.whl 23 | npm login 24 | for tarball in deathbeds-jupyterlab-font*.tgz; do 25 | npm publish $tarball 26 | done 27 | npm logout 28 | ``` 29 | - [ ] postmortem 30 | - [ ] handle `conda-forge` feedstock tasks 31 | - [ ] validate on binder via simplest-possible gists 32 | - [ ] bump to next development version 33 | - [ ] update release procedures 34 | -------------------------------------------------------------------------------- /atest/Notebook.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation The font editor allows changing fonts in notebooks 3 | 4 | Resource ./_keywords.resource 5 | 6 | Test Tags app:lab 7 | 8 | 9 | *** Test Cases *** 10 | Notebook Font Editor 11 | [Documentation] Customize Notebook fonts with the Font Editor 12 | [Template] Use the font editor to configure fonts 13 | [Setup] Open the Notebook Font Editor 14 | Notebook Code - 15 | Notebook Code Anonymous Pro Bold 16 | Notebook Code Anonymous Pro Regular 17 | Notebook Code DejaVu Sans Mono 18 | Notebook Code DejaVu Sans Mono Bold 19 | Notebook Code Fira Code Bold 20 | Notebook Code Fira Code Light 21 | Notebook Code Fira Code Medium 22 | Notebook Code Fira Code Regular 23 | Notebook Content - 24 | Notebook Content Atkinson Hyperlegible Regular 25 | Notebook Content Atkinson Hyperlegible Bold 26 | Notebook UI - 27 | Notebook UI Atkinson Hyperlegible Regular 28 | Notebook UI Atkinson Hyperlegible Bold 29 | [Teardown] Close the Font Editor Untitled 30 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-anonymous-pro/src/index.ts: -------------------------------------------------------------------------------- 1 | import { makePlugin } from '@deathbeds/jupyterlab-fonts'; 2 | 3 | const plugin = makePlugin({ 4 | id: '@deathbeds/jupyterlab-font-anonymous-pro', 5 | fontName: 'Anonymous Pro', 6 | license: { 7 | spdx: 'OFL-1.1', 8 | name: 'SIL Open Font License 1.1', 9 | holders: [ 10 | `Copyright (c) 2009, Mark Simonson (http://www.ms-studio.com, mark@marksimonson.com), with Reserved Font Name Anonymous Pro Minus.`, 11 | ], 12 | }, 13 | licenseText: async () => { 14 | return (await import('!!raw-loader!../vendor/anonymous-pro/LICENSE')).default; 15 | }, 16 | variants: async () => { 17 | return { 18 | Regular: [ 19 | { 20 | woff2: () => 21 | import( 22 | `!!file-loader!@fontsource/atkinson-hyperlegible/files/atkinson-hyperlegible-latin-400-normal.woff2` 23 | ), 24 | }, 25 | ], 26 | Bold: [ 27 | { 28 | woff2: () => 29 | import( 30 | `!!file-loader!typeface-anonymous-pro/files/anonymous-pro-latin-700.woff2` 31 | ), 32 | }, 33 | ], 34 | }; 35 | }, 36 | }); 37 | 38 | export default plugin; 39 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/style/icons/copyright.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023, Dead Pixels Collective 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of staged-recipes nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-fira-code/src/index.ts: -------------------------------------------------------------------------------- 1 | import { makePlugin } from '@deathbeds/jupyterlab-fonts'; 2 | 3 | const plugin = makePlugin({ 4 | id: '@deathbeds/jupyterlab-font-fira-code', 5 | fontName: 'Fira Code', 6 | license: { 7 | spdx: 'OFL-1.1', 8 | name: 'SIL Open Font License 1.1', 9 | holders: [ 10 | `Copyright (c) 2014, Nikita Prokopov http://tonsky.me with Reserved Font Name Fira Code.`, 11 | `Copyright (c) 2014, Mozilla Foundation https://mozilla.org/ with Reserved Font Name Fira Sans.`, 12 | `Copyright (c) 2014, Mozilla Foundation https://mozilla.org/ with Reserved Font Name Fira Mono.`, 13 | 'Copyright (c) 2014, Telefonica S.A.', 14 | ], 15 | }, 16 | licenseText: async () => { 17 | return (await import('!!raw-loader!firacode/LICENSE')).default; 18 | }, 19 | variants: async () => { 20 | return { 21 | Light: [ 22 | { 23 | woff2: () => 24 | import(`!!file-loader!firacode/distr/woff2/FiraCode-Light.woff2`), 25 | }, 26 | ], 27 | Regular: [ 28 | { 29 | woff2: () => 30 | import(`!!file-loader!firacode/distr/woff2/FiraCode-Regular.woff2`), 31 | }, 32 | ], 33 | Medium: [ 34 | { 35 | woff2: () => 36 | import(`!!file-loader!firacode/distr/woff2/FiraCode-Medium.woff2`), 37 | }, 38 | ], 39 | Bold: [ 40 | { 41 | woff2: () => import(`!!file-loader!firacode/distr/woff2/FiraCode-Bold.woff2`), 42 | }, 43 | ], 44 | }; 45 | }, 46 | }); 47 | 48 | export default plugin; 49 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023, Dead Pixels Collective 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of staged-recipes nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-fira-code/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023, Dead Pixels Collective 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of staged-recipes nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-anonymous-pro/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023, Dead Pixels Collective 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of staged-recipes nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-dejavu-sans-mono/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023, Dead Pixels Collective 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of staged-recipes nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /packages/jupyterlab-font-atkinson-hyperlegible/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023, Dead Pixels Collective 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of staged-recipes nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /atest/Menus.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Test whether the JupyterLab Fonts Menu performs as advertised. 3 | 4 | Resource ./_keywords.resource 5 | 6 | Suite Setup Prepare Menu Test 7 | 8 | Test Tags app:lab 9 | 10 | 11 | *** Test Cases *** 12 | Customize code font with the JupyterLab Menu 13 | [Documentation] Try out some font settings with the menu 14 | [Template] Use the Menu to configure Font 15 | Code Font Default Code Font 16 | Code Font Anonymous Pro Bold 17 | Code Font Anonymous Pro Regular 18 | Code Font DejaVu Sans Mono 19 | Code Font DejaVu Sans Mono Bold 20 | Code Font Fira Code Bold 21 | Code Font Fira Code Light 22 | Code Font Fira Code Medium 23 | Code Font Fira Code Regular 24 | Code Font Default Code Font 25 | Code Line Height Default Code Line Height 26 | Code Line Height 2 27 | Code Line Height Default Code Line Height 28 | Code Size Default Code Size 29 | Code Size 20px 30 | Code Size Default Code Size 31 | Content Font Default Content Font 32 | Content Font Atkinson Hyperlegible Regular 33 | Content Font Atkinson Hyperlegible Bold 34 | Content Font Default Content Font 35 | Content Line Height Default Content Line Height 36 | Content Line Height 2 37 | Content Line Height Default Content Line Height 38 | Content Size Default Content Size 39 | Content Size 20px 40 | Content Size Default Content Size 41 | UI Font Default UI Font 42 | UI Font Atkinson Hyperlegible Regular 43 | UI Font Atkinson Hyperlegible Bold 44 | UI Font Default UI Font 45 | -------------------------------------------------------------------------------- /.github/specs/__lock__.toml: -------------------------------------------------------------------------------- 1 | prefix = "lock" 2 | 3 | [paths] 4 | all_specs = [":rglob::.::*.yml"] 5 | all_specs_to_lock = [":glob::.::*.yml::!_"] 6 | lock_py = ["__lock__.py"] 7 | lock_toml = ["__lock__.toml"] 8 | 9 | [tokens] 10 | subdirs = ["linux-64", "osx-64", "win-64"] 11 | 12 | [tasks.preflight] 13 | doc = "generate a description of the lockfiles that will be created" 14 | file_dep = ["::all_specs", "::lock_py", "::lock_toml"] 15 | targets = ["::dt::lock_preflight_yml"] 16 | actions = [ 17 | {py={"__lock__:preflight" = {args=[ 18 | "::dt::lock_preflight_yml", "::all_specs_to_lock" 19 | ], kwargs={subdirs=["::subdirs"]}}}} 20 | ] 21 | 22 | [templates.jinja2.tasks.solve] 23 | yaml = """ 24 | {% for header in paths["dt:lock_headers"] %} 25 | {% set stem = header.split("/")[-1].replace(".txt", "") %} 26 | {{ stem }}: 27 | file_dep: 28 | - {{ header }} 29 | - ::dt::lock_preflight_yml 30 | targets: 31 | - {{ paths["dt:env_lock_dir"][0] }}/{{ stem }}.conda.lock 32 | actions: 33 | - py: 34 | __lock__:lock_from_preflight: 35 | args: 36 | - ::dt::env_lock_dir 37 | - {{ header }} 38 | - ::dt::lock_preflight_yml 39 | {% endfor %} 40 | """ 41 | 42 | [templates.jinja2.tasks.install] 43 | yaml = """ 44 | {% for header in paths["dt:lock_headers"] %} 45 | {% set stem = header.split("/")[-1].replace(".txt", "") %} 46 | {{ stem }}: 47 | meta: 48 | doitoml: 49 | cwd: ../../ 50 | skip: "${DEMO_IN_BINDER}" 51 | file_dep: 52 | - {{ paths["dt:env_lock_dir"][0] }}/{{ stem }}.conda.lock 53 | targets: 54 | - ../../.envs/{{ stem }}/conda-meta/history 55 | actions: 56 | - - mamba 57 | - create 58 | - --prefix 59 | - .envs/{{ stem }} 60 | - --file 61 | - {{ paths["dt:env_lock_dir"][0] }}/{{ stem }}.conda.lock 62 | {% endfor %} 63 | """ 64 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/src/button.ts: -------------------------------------------------------------------------------- 1 | import { ToolbarButton } from '@jupyterlab/apputils'; 2 | import { DocumentRegistry } from '@jupyterlab/docregistry'; 3 | import { NotebookPanel, INotebookModel } from '@jupyterlab/notebook'; 4 | import { IDisposable, DisposableDelegate } from '@lumino/disposable'; 5 | import { ISignal, Signal } from '@lumino/signaling'; 6 | 7 | import { ICONS } from './icons'; 8 | import * as compat from './labcompat'; 9 | import { PACKAGE_NAME, CONFIGURED_CLASS } from './tokens'; 10 | 11 | /** 12 | * A notebook widget extension that adds a button to the toolbar. 13 | */ 14 | export class NotebookFontsButton 15 | implements DocumentRegistry.IWidgetExtension 16 | { 17 | readonly widgetRequested: ISignal = new Signal(this); 18 | /** 19 | * Create a new extension object. 20 | */ 21 | createNew( 22 | panel: NotebookPanel, 23 | context: DocumentRegistry.IContext, 24 | ): IDisposable { 25 | let button = new ToolbarButton({ 26 | icon: ICONS.fonts, 27 | onClick: () => { 28 | (this.widgetRequested as Signal).emit(void 0); 29 | }, 30 | tooltip: 'Customize Notebook Fonts', 31 | }); 32 | 33 | const metaUpdated = () => { 34 | const metadata = panel.model 35 | ? compat.getPanelMetadata(panel.model, PACKAGE_NAME) 36 | : null; 37 | if (metadata) { 38 | button.addClass(CONFIGURED_CLASS); 39 | } else { 40 | button.removeClass(CONFIGURED_CLASS); 41 | } 42 | }; 43 | 44 | const panelModel = panel.model; 45 | 46 | if (panelModel) { 47 | compat.metadataSignal(panelModel).connect(metaUpdated); 48 | metaUpdated(); 49 | } 50 | 51 | panel.toolbar.insertItem(9, 'fonts', button); 52 | 53 | return new DisposableDelegate(() => { 54 | if (panelModel) { 55 | compat.metadataSignal(panelModel).disconnect(metaUpdated); 56 | } 57 | button.dispose(); 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v3.0.1 4 | 5 | > TBD 6 | 7 | ## v3.0.0 8 | 9 | - see `3.0.0` alpha releases below 10 | 11 | ## v3.0.0a3 12 | 13 | - hoist, update Lab 3/4 compatibility methods to public API 14 | - fix notebook metadata watching when no app-level styles are present 15 | 16 | ## v3.0.0a2 17 | 18 | - add more Lab 3/4 compatibility methods 19 | 20 | ## v3.0.0a1 21 | 22 | - remove tests and duplicated assets from wheel 23 | - fix npm version numbers 24 | - lazily load JSS and friends 25 | 26 | ## v3.0.0a0 27 | 28 | - Support JupyterLab 4 29 | - Support Notebook 7 30 | - Fix global typefaces 31 | - Adds UI font to the editor, commands, and menus 32 | - Adds Atkinson Hyperlegible font 33 | 34 | ## v2.1.1 35 | 36 | - Fix some errors when disposing notebooks 37 | - Normalize generated CSS 38 | - Allow for dereferencing local asset `url()`s for `@import`, etc. 39 | 40 | ## v2.1.0 41 | 42 | - Improve notebook-level `@import`, `@font-face`, etc. 43 | - adds `data-jpf-cell-id` and `data-jpf-cell-tags` to notebook cell elements 44 | - this allows notebook cells to carry their own style 45 | - `IFontManager` supports `setTransientNotebookStyle` for dynamic styling 46 | 47 | ## v2.0.0 48 | 49 | - Support JupyterLab 3 50 | - Installs as a prebuilt extension, no `nodejs` required 51 | 52 | ## v1.0.1 53 | 54 | - Fixes issue with global setting 55 | 56 | ## v1.0.0 57 | 58 | - Support JupyterLab 2.0 59 | 60 | ## v0.7.0 61 | 62 | - Support JupyterLab 1.0.0 63 | 64 | ## v0.6.0 65 | 66 | - Support JupyterLab 0.35 67 | 68 | ## v0.5.0 69 | 70 | - Support JupyterLab 0.34 71 | 72 | ## v0.4.1 73 | 74 | - Restore content font family in editor 75 | 76 | ## v0.4.0 77 | 78 | - Support JupyterLab 0.33 79 | 80 | ## v0.3.0 81 | 82 | - store font data in notebooks 83 | - licenses 84 | - woff2 as base64-encoded 85 | 86 | ## v0.2.0 87 | 88 | - add persistent configuration 89 | - add command palette items 90 | - adopt jss 91 | 92 | ## v0.1.1 93 | 94 | - initial release 95 | - fira code in four weights 96 | - nasty hacks 97 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/src/license.tsx: -------------------------------------------------------------------------------- 1 | import { VDomModel, VDomRenderer } from '@jupyterlab/apputils'; 2 | import * as React from 'react'; 3 | 4 | import { IFontFaceOptions } from './tokens'; 5 | 6 | import '../style/license.css'; 7 | 8 | const WRAPPER_CLASS = 'jp-LicenseViewer-wrapper'; 9 | const LICENSE_CLASS = 'jp-LicenseViewer'; 10 | 11 | export class LicenseViewer extends VDomRenderer { 12 | constructor(options: LicenseViewer.IOptions) { 13 | super(new LicenseViewer.Model(options)); 14 | } 15 | protected render(): React.ReactElement { 16 | this.addClass(WRAPPER_CLASS); 17 | let m = this.model; 18 | 19 | // Bail if there is no model. 20 | if (!m) { 21 | return <>; 22 | } 23 | 24 | const text = m.licenseText ?
{m.licenseText}
: <>; 25 | 26 | return ( 27 |
28 |

{m.font.name}

29 |

{m.font.license.name}

30 | {text} 31 |
32 | ); 33 | } 34 | } 35 | 36 | export namespace LicenseViewer { 37 | export interface IOptions { 38 | font: IFontFaceOptions; 39 | } 40 | 41 | export class Model extends VDomModel { 42 | private _font: IFontFaceOptions; 43 | private _licenseText: string; 44 | private _licenseTextPromise: Promise; 45 | 46 | constructor(options: IOptions) { 47 | super(); 48 | this.font = options.font; 49 | } 50 | 51 | get font() { 52 | return this._font; 53 | } 54 | 55 | set font(font) { 56 | this._font = font; 57 | this.stateChanged.emit(void 0); 58 | this._licenseTextPromise = new Promise((resolve, reject) => { 59 | this._font.license 60 | .text() 61 | .then((licenseText) => { 62 | this._licenseText = licenseText; 63 | this.stateChanged.emit(void 0); 64 | resolve(this._licenseText); 65 | }) 66 | .catch((err) => { 67 | reject(err); 68 | }); 69 | }); 70 | } 71 | 72 | get licenseText() { 73 | return this._licenseText; 74 | } 75 | 76 | get licenseTextPromise() { 77 | return this._licenseTextPromise; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /atest/_variables.resource: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Variables for testing jupyterlab-fonts 3 | 4 | 5 | *** Variables *** 6 | ${ED} css:.jp-FontsEditor 7 | ${DOCK} //div[@id='jp-main-dock-panel'] 8 | ${TAB} li[contains(@class, 'lm-TabBar-tab')] 9 | ${ICON_FONT} *[@data-icon = 'fonts:fonts'] 10 | ${ICON_LICENSE} *[@data-icon = 'fonts:license'] 11 | ${ICON_SETTINGS} *[@data-icon = 'ui-components:settings'] 12 | ${ICON_NOTEBOOK} *[@data-icon = 'ui-components:notebook'] 13 | ${ICON_CLOSE} div[contains(@class, 'lm-TabBar-tabCloseIcon')] 14 | ${BUTTON} .jp-FontsEditor-button 15 | ${SETTING_ITEM} //div[contains(@class, 'jp-PluginList')]//div 16 | ${SETTINGS_RAW} .jp-SettingsRawEditor-user 17 | ${SHEET} .jp-Fonts-Sheet 18 | ${MOD_GLOBAL} .jp-fonts-mod-global 19 | ${MOD_NOTEBOOK} .jp-fonts-mod-notebook 20 | ${DATA_TAGS} data-jpf-cell-tags 21 | ${DATA_CELL_ID} data-jpf-cell-id 22 | # lab 23 | ${CSS_LAB_MOD_HIDDEN} .lm-mod-hidden 24 | ${CSS_LAB_CELL_META_JSON_CM} .jp-MetadataEditorTool .CodeMirror 25 | ${CSS_LAB_CELL_META_JSON_CM_HIDDEN} ${CSS_LAB_MOD_HIDDEN} ${CSS_LAB_CELL_META_JSON_CM} 26 | ${CSS_LAB_ADVANCED_COLLAPSE} .jp-NotebookTools .jp-Collapse-header 27 | ${CSS_LAB_CELL_META_TOOL} .jp-RankedPanel .jp-MetadataEditorTool:nth-child(1) 28 | ${CSS_LAB_NOTEBOOK_META_TOOL} .jp-RankedPanel .jp-MetadataEditorTool:nth-child(2) 29 | ${CSS_LAB_META_COMMIT} [data-icon="ui-components:check"] 30 | # lab4/nb7 31 | ${XP_LAB4_COLLAPSED_PANEL} //*[contains(@class, 'jp-Collapse-header-collapsed')] 32 | ${XP_LAB4_COLLAPSED_PANEL_TITLE} ${XP_LAB4_COLLAPSED_PANEL}//*[contains(@class, 'jp-Collapser-title')] 33 | ${CSS_LAB4_OPEN_IN_NOTEBOOK} .jp-ToolbarButtonComponent[data-command="jupyter-notebook:open-notebook"] 34 | # rfjl bugs 35 | ${CM CSS EDITOR} .CodeMirror 36 | -------------------------------------------------------------------------------- /packages/jupyterlab-fonts/src/labcompat.ts: -------------------------------------------------------------------------------- 1 | import type { ICellModel } from '@jupyterlab/cells'; 2 | import type { INotebookModel } from '@jupyterlab/notebook'; 3 | import type { ISignal } from '@lumino/signaling'; 4 | 5 | export function metadataSignal(panelModel: INotebookModel): ISignal { 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 | --------------------------------------------------------------------------------