Custom (google) fonts in deployed app with local font files .woff / .woff2 / .tff

Hi!

I run a deployed app from a private github repo and I am trying to add custom fonts (google and non-google fonts) to it. However, I need to serve the font via local files directly, since providing them via a google fonts api call causes GDPR problems and for the other fonts I just have the files. I’ve downloaded the respective .tff/.woff/.woff2 files and am now trying to include them but the font will not be updated.

My folder structure is:
.streamlit
static
fonts
----fira-sans-v17-latin-100.woff2
---- all other font styles / data formats
style.css
app.py

My style.css looks like this:

h1 {
    font-family: "Fira Sans";
    font-style: normal;
    font-weight: 100;
    }
@font-face {
    font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
    font-family: 'Fira Sans';
    font-style: normal;
    font-weight: 100;
    src: url('../fonts/fira-sans-v17-latin-100.eot'); /* IE9 Compat Modes */
    src: url('../fonts/fira-sans-v17-latin-100.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
         url('../fonts/fira-sans-v17-latin-100.woff2') format('woff2'), /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
         url('../fonts/fira-sans-v17-latin-100.woff') format('woff'), /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+, iOS 5+ */
         url('../fonts/fira-sans-v17-latin-100.ttf') format('truetype'), /* Chrome 4+, Firefox 3.5+, IE 9+, Safari 3.1+, iOS 4.2+, Android Browser 2.2+ */
         url('../fonts/fira-sans-v17-latin-100.svg#FiraSans') format('svg'); /* Legacy iOS */
  }

and in app.py I am using the “css-hack”:

import streamlit as st
with open("style.css") as f:
        st.markdown('<style>{}</style>'.format(f.read()), unsafe_allow_html=True)
st.title("This is a header")

However, just the font-weight and font-style render but not the font-family. I guess there might be a problem with the relative path but I cant figure it out. I also tried to move the style.css and the font files to the static folder but no success this way either…Additionally I tried to use streamlit.components html

import streamlit as st
import streamlit.components.v1 as components

components.html("""<!DOCTYPE html>
    <html>
    <head>
    <link rel="stylesheet" href="style.css">
    </head>
    <body>

    <h1>This is a heading</h1>

    </body>
    </html>"""

but still the same problem. Does anybody have an idea on how to solve this?

Hi @ksbiqo

You can check out the code snippet solution from @blackary on using a custom font in a local app:

Hope this helps!

Hi @dataprofessor ,
thanks for your answer, I actually also tried this already and it did not work out for my case (FileNotFoundError: [Errno 2]) :confused: also my app is deployed so I need to have a solution which works for deployed apps as well, any other idea? I mean it should be possible to reference a local file font somehow. When I just set up an html document and reference the stylesheet as above it works fine. However, in my streamlit app the font won’t change…

I think you got it right that the problem is the file paths – you should be able to get this working using Static file serving<!-- --> - Streamlit Docs

  1. Put the files in a static folder
  2. Enable static file serving
  3. Update your urls in the css to point to the static files
1 Like

Hi @blackary !

Thanks for your answer. I enabled static file serving, put the files in the folder and updated the css as you suggested. When I now just browse the link which points to the static file, the file gets downloaded automatically, so it seems to be the right path/link, however, the fonts still don’t render :neutral_face: do you have any other idea I could try?

Could you share a simplified script that shows the way you’re trying to use the fonts?

Also could you look in your browser debug console (e.g. Open Chrome DevTools  |  Chrome for Developers) and see if there look like relevant errors?

I tried the following in my style.css:

@font-face {
    font-display: swap; 
    font-family: 'FiraSans';
    font-style: normal;
    font-weight: 600;
    src: url('https://nur-mut.streamlit.app/app/static/fira-sans-v17-latin-600.eot'); /* IE9 Compat Modes */
    src: url('https://nur-mut.streamlit.app/app/static/fira-sans-v17-latin-600.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
         url('https://nur-mut.streamlit.app/app/static/fira-sans-v17-latin-600.woff2') format('woff2'), /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
         url('https://nur-mut.streamlit.app/app/static/fira-sans-v17-latin-600.woff') format('woff'), /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+, iOS 5+ */
         url('https://nur-mut.streamlit.app/app/static/fira-sans-v17-latin-600.ttf') format('truetype'), /* Chrome 4+, Firefox 3.5+, IE 9+, Safari 3.1+, iOS 4.2+, Android Browser 2.2+ */
         url('https://nur-mut.streamlit.app/app/static/fira-sans-v17-latin-600.svg#FiraSans') format('svg'); /* Legacy iOS */
  }

h1 {
    font-family: 'FiraSans', sans-serif;
    font-style: normal;
    font-weight: 600;
    color: #13B8CC;
}

I can also see the problem occurring in the dev tools, it says “Failed to decode downloaded font”

I also tried and downloaded the font files from somewhere else and tried to use just the woff2 file but still no success. In the dev tools console I also see this error, which I could not find a lot of documentation about:

My best guess is that is because streamlit is serving those files as plain text Static file serving - Streamlit Docs, and that’s causing issues.

To check that, use devtools. Look at the network tab when the page loads (either fonts file directly or the page with the CSS that loads them). Click on the request for the font file, and check the response headers. If the font is being served plain text, that’s probably the issue.

I’ve found that putting streamlit behind an Nginx reverse proxy is super helpful for these types of situations. Instead of using built in static file serving, I can do it myself with Nginx or Flask. Additionally, I can inject js and CSS into the page before streamlit does anything, allowing me to apply custom styles and parent level js without the annoying issue of streamlit flickering the initial styles first and then having my custom styles applied after the page has loaded.

2 Likes