Custom component with custom fonts

I am trying to create a custom component that loads custom fonts. If I load the fonts via the Dash app itself, i.e. add the necessary .css files with @font-face definitions and font files, it works. However, if I move the font definition CSS and the font file to a custom component, it no longer works. Has anyone tried to do something similar? Did you get it to work? How?

If I use fonts available online and use a link in the font definition (rather than bundling the font with the app/component), everything works just fint.

Hello @Emil,

I have something where I add a custom set of css files.

Are you importing directly into the component? I ended up importing it into the index.js, and made sure it was at the same level as the index.js.

1 Like

I tried both directly into the component, and via the index.css approach. In both cases, I am able to get the .css working (e.g. changing font color), but not the fonts.

I guess the problem lies somewhere in the url mapping and/or how the font files are served.

Does it give any import errors in the console?

I ran into issues when trying to dynamically import packages.

Have you looked here:

Here looks promising:


It looks like the main thing is to not just import into the index.css but also import the fonts themselves into the index.js.

I dont’ see any errors on the console. I can also see from the files generated, that the font files are getting bundled with the Python code,

image

Yes, those were actually some of the guides that I consulted to get started. For the 3-way links I can’t use approaches 1&2, as the fonts are proprietary, so I am using approach 3.

@Emil,

Just to confirm, you are importing a .ttf file?

I’m asking because I’ll take a look at and see if I can get something to work using it the same way.

Also, are you using the cookie-cutter dash component?

I am using .woff files, but I wouldn’t expect the fileformat to matter. I have started from the TypeScript template. Besides this issue, it’s a great template. I am already considering to rewrite dash-leaflet with it - but that would take me some time…

1 Like

Who has time to reinvent the wheel. Haha.

I’ll have to check it out.

1 Like

I got it!

OMG… Haha :stuck_out_tongue:

^^ This helped

structure:
image

index.css:

@font-face{
    font-family: Comfortaa;
    src: local('Comfortaa'),
    url(../assets/fonts/comfortaa-normal-normal.ttf) format('truetype');
    font-weight: normal;
}

@font-face{
    font-family: Comfortaa;
    src: local('Comfortaa'),
    url(../assets/fonts/comfortaa-bold-normal.ttf) format('truetype');
    font-weight: bold;
}

.testing {
    font-family: Comfortaa;
    font-size: 25px;
}

index.js:

import '../assets/fonts/comfortaa-normal-normal.ttf';
import '../assets/fonts/comfortaa-bold-normal.ttf';

index.ts:

import Testing from './components/Testing';
import './components/index.css';
import './components/index.js';

export {
    Testing
}

additional module rules in webpack.config.js (additional npm npm install file-loader --save-dev):

{
                    test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
                    use: [
                      {
                        loader: 'file-loader',
                        options: {
                          name: '[name].[ext]',
                          outputPath: 'fonts/'
                        }
                      }
                    ]
                  }

usage.py:

import testing
import dash
from dash import html

app = dash.Dash(__name__)

app.layout = html.Div(
    [
        testing.Testing(id='component', children='testing-font', className='testing'),
        testing.Testing(id='component_two', children='testing-no-font')
    ]
)


if __name__ == '__main__':
    app.run_server(debug=True, port=1234)

result:
image

1 Like

Awesome! Could you provide a link to the repo where you got it working?

Sure, here you go:

I know, I messed up with the cookie-cutter, haha. XD

1 Like

Haha, yes, it looks like something went wrong with the cookie cutter :laughing:. I tried cloning your repo, setting up the Python env, installing packages via npm, and running the example. However, it doesn’t seem to work for me,

Do you have an assets folder with the assets next to your app.py file?

Nope, its under the ts folder.

Strange that it doesn’t work on my system. And you don’t get any console errors?

Wait… it might be because I have it downloaded on my computer… but I wouldnt think that would be the case because it wasnt working before hand…

Let me try with maybe a random font…

That might be. I can see you includedsrc: local('Comfortaa') in the font definition, I think that will cause a load from your local system, if the font exists. Could you try without that part (and/or try with another font)?

1 Like

Yeah… no go. :frowning:

Even then, it still looks like it’s trying to load from a path that is externally viewable.

So, not entirely sure what the benefits of trying to get it working this way is.

@Emil,

So, I got somewhere, I’m thinking you need to add the routing in the _init_.py for the path that is trying to load. Since this doesn’t happen automatically.

I could get it to show the path was working, but was getting an error for it not being able to decode the downloaded font. Which means that the font was transmitted in a format that was unexpected or the file wasn’t loaded at all and I just made the path work. Haha.

@Emil You ever figure this out? Kinda stuck on a similar issue when trying to build a new component. Trying to set it up to automatically include these files into the package but haven’t been able to figure out a proper way to set that up.

Setup my webpack.config.js as:

const path = require('path');
const webpack = require('webpack');
const WebpackDashDynamicImport = require('@plotly/webpack-dash-dynamic-import');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const fs = require('fs');

const packagejson = require('./package.json');

const dashLibraryName = packagejson.name.replace(/-/g, '_');

module.exports = (env, argv) => {
    const mode = argv && argv.mode || 'production';
    const filename = `${dashLibraryName}.${mode === 'development' ? 'dev' : 'min'}.js`;

    // Check if the icons directory exists
    const iconsDir = path.resolve(__dirname, 'node_modules', '@blueprintjs', 'icons', 'resources', 'icons');
    const iconsDirExists = fs.existsSync(iconsDir);

    const copyPatterns = [
        { from: 'src/lib/components/assets', to: 'assets' },
        { from: 'node_modules/@blueprintjs/core/lib/css', to: 'assets/css' },
        { from: 'node_modules/@blueprintjs/icons/lib/css', to: 'assets/css' },
    ];

    // Only add the icons directory if it exists
    if (iconsDirExists) {
        copyPatterns.push({ from: iconsDir, to: 'assets/icons' });
    }

    return {
        mode,
        entry: {main: './src/lib/index.js'},
        output: {
            path: path.resolve(__dirname, dashLibraryName),
            filename,
            library: dashLibraryName,
            libraryTarget: 'window',
        },
        devtool: 'source-map',
        externals: {
            react: 'React',
            'react-dom': 'ReactDOM',
            'plotly.js': 'Plotly',
            'prop-types': 'PropTypes',
        },
        module: {
            rules: [
                {
                    test: /\.jsx?$/,
                    exclude: /node_modules/,
                    use: {
                        loader: 'babel-loader',
                    },
                },
                {
                    test: /\.css$/,
                    use: ['style-loader', 'css-loader'],
                },
                {
                    test: /\.(woff|woff2|eot|ttf|otf)$/,
                    use: [
                        {
                            loader: 'file-loader',
                            options: {
                                name: '[name].[ext]',
                                outputPath: 'assets/fonts/',
                            },
                        },
                    ],
                },
                {
                    test: /\.svg$/,
                    use: [
                        {
                            loader: 'file-loader',
                            options: {
                                name: '[name].[ext]',
                                outputPath: 'assets/icons/',
                            },
                        },
                    ],
                },
            ],
        },
        resolve: {
            extensions: ['.js', '.jsx']
        },
        plugins: [
            new WebpackDashDynamicImport(),
            new webpack.SourceMapDevToolPlugin({
                filename: '[file].map',
                exclude: ['async-plotlyjs']
            }),
            new CopyWebpackPlugin({
                patterns: copyPatterns,
            }),
        ]
    };
};

setup my package.js like:

{
  "name": "dash_mosaic",
  "version": "0.0.1",
  "description": "react mosaic for dash",
  "repository": {
    "type": "git",
    "url": "git://github.com/pip-install-python/dash-mosaic.git"
  },
  "bugs": {
    "url": "https://github.com/pip-install-python/dash-mosaic/issues"
  },
  "homepage": "https://github.com/pip-install-python/dash-mosaic",
  "main": "build/index.js",
  "scripts": {
    "start": "webpack serve --config ./webpack.serve.config.js --open",
    "validate-init": "python _validate_init.py",
    "prepublishOnly": "npm run validate-init",
    "build:js": "webpack --mode production",
    "build:backends": "dash-generate-components ./src/lib/components dash_mosaic -p package-info.json --r-prefix '' --jl-prefix '' --ignore \\.test\\.",
    "build:backends-activated": "(. venv/bin/activate || venv\\scripts\\activate && npm run build:backends)",
    "build": "npm run build:js && npm run build:backends",
    "build:activated": "npm run build:js && npm run build:backends-activated"
  },
  "author": "Pip Install Python <pipinstallpython@gmail.com>",
  "license": "MIT",
  "dependencies": {
    "@blueprintjs/core": "^5.13.0",
    "@blueprintjs/icons": "^5.13.0",
    "lodash": "^4.17.21",
    "ramda": "^0.26.1",
    "react-mosaic-component": "^6.1.0"
  },
  "devDependencies": {
    "@babel/core": "^7.22.1",
    "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
    "@babel/preset-env": "^7.22.2",
    "@babel/preset-react": "^7.22.3",
    "@plotly/dash-component-plugins": "^1.2.3",
    "@plotly/webpack-dash-dynamic-import": "^1.2.0",
    "@svgr/webpack": "^6.5.1",
    "babel-eslint": "^10.1.0",
    "babel-loader": "^9.1.2",
    "copy-webpack-plugin": "^9.1.0",
    "copyfiles": "^2.1.1",
    "css-loader": "^6.8.1",
    "eslint": "^6.0.1",
    "eslint-config-prettier": "^6.0.0",
    "eslint-plugin-import": "^2.18.0",
    "eslint-plugin-react": "^7.14.2",
    "file-loader": "^6.2.0",
    "prop-types": "^15.8.1",
    "react": "^18.3.1",
    "react-docgen": "^5.4.3",
    "react-dom": "^18.3.1",
    "style-loader": "^3.3.3",
    "styled-jsx": "^5.1.6",
    "webpack": "^5.84.1",
    "webpack-cli": "^5.1.1",
    "webpack-dev-server": "^4.15.0"
  },
  "engines": {
    "node": ">=8.11.0",
    "npm": ">=6.1.0"
  },
  "componentProperties": {
    "DashMosaic": {
      "props": {
        "id": {
          "type": {
            "name": "string"
          },
          "required": false,
          "description": "The ID used to identify this component in Dash callbacks."
        },
        "layout": {
          "type": {
            "name": "object"
          },
          "required": false,
          "description": "The layout configuration for the mosaic."
        },
        "theme": {
          "type": {
            "name": "string"
          },
          "required": false,
          "description": "The theme to apply to the mosaic."
        },
        "tileContent": {
          "type": {
            "name": "object"
          },
          "required": false,
          "description": "An object containing the content for each tile."
        },
        "style": {
          "type": {
            "name": "object"
          },
          "required": false,
          "description": "Inline style to apply to the mosaic container."
        }
      }
    }
  }

when i try and import those woff2, woff, ttf and main files in either the index.js or the .react.js file it seems to break but if I put those files in the root/assets folder they work and display correctly. But when I try and run python setup.py sdist bdist_wheel when I try and run pip install dash_mosaic-0.0.1.tar.gz the component works but the assets are not included.

Tried to follow your comments @jinnyzor and get this setup but wasn’t able to also seems like The ultimate learning resource for creative web development and web apps was taken down.