Roboto font 404 Not Found

I would like to use the Roboto font in my perspective views, but I keep getting 404 Not Found responses on the requests for the font. In the gateway logs, I can see the following error from the ThemeManager and warning from the AbstractAssetManager:

Theme Manager Error

Unable to parse theme at path data/modules/com.inductiveautomation.perspective/themes/light/.fonts.css.swp
java.nio.charset.MalformedInputException: Input length = 1
at java.base/java.lang.StringCoding.throwMalformed(Unknown Source)
at java.base/java.lang.StringCoding.decodeUTF8_0(Unknown Source)
at java.base/java.lang.StringCoding.newStringNoRepl1(Unknown Source)
at java.base/java.lang.StringCoding.newStringNoRepl(Unknown Source)
at java.base/java.lang.System$2.newStringNoRepl(Unknown Source)
at java.base/java.nio.file.Files.readString(Unknown Source)
at java.base/java.nio.file.Files.readString(Unknown Source)
at com.inductiveautomation.perspective.gateway.assets.themes.ThemeManagerImpl.parseValue(ThemeManagerImpl.java:233)
at com.inductiveautomation.perspective.gateway.assets.AbstractAssetManager.onCreateOrModify(AbstractAssetManager.java:456)
at com.inductiveautomation.perspective.gateway.assets.AbstractAssetManager.onWatchEvent(AbstractAssetManager.java:160)
at com.inductiveautomation.perspective.gateway.files.FileWatcher.processWatchEvent(FileWatcher.java:204)
at com.inductiveautomation.perspective.gateway.files.FileWatcher.lambda$processWatchKey$0(FileWatcher.java:234)
at java.base/java.util.ArrayList.forEach(Unknown Source)
at com.inductiveautomation.perspective.gateway.files.FileWatcher.processWatchKey(FileWatcher.java:234)
at com.inductiveautomation.perspective.gateway.files.FileWatcher.processFileChanges(FileWatcher.java:260)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at com.inductiveautomation.perspective.gateway.threading.BlockingWork$BlockingWorkRunnable.run(BlockingWork.java:58)
at java.base/java.lang.Thread.run(Unknown Source)

AbstractAssetManager Warning

Unable to update cache for modified file 'data/modules/com.inductiveautomation.perspective/themes/light/.fonts.css.swp' - cache for this library will remain unchanged

The light/fonts.css file looks like this, and always gets reset to this after a gateway restart:

@font-face {
    font-family: "Noto Sans";
    font-style: normal;
    font-weight: 400;
    src: local('Noto Sans'), local('NotoSans'),
         url('/data/perspective/fonts/noto-sans-v8-latin-regular.ttf') format('truetype');
}

@font-face {
    font-family: "Noto Sans";
    font-style: normal;
    font-weight: 500;
    src: local('Noto Sans Medium'), local('NotoSans-Medium'),
         url('/data/perspective/fonts/noto-sans-v8-latin-medium.ttf') format('truetype');
}

@font-face {
    font-family: "Noto Sans";
    font-style: normal;
    font-weight: 700;
    src: local('Noto Sans Bold'), local('NotoSans-Bold'),
         url('/data/perspective/fonts/noto-sans-v8-latin-700.ttf') format('truetype');
}

@font-face {
    font-family: "Roboto";
    src: url("/data/perspective/fonts/Roboto-Regular.woff");
}

@font-face {
    font-family: "Roboto";
    font-weight: 500;
    src: url("/data/perspective/fonts/Roboto-Medium.woff");
}

@font-face {
    font-family: "Roboto";
    font-weight: 300;
    src: url("/data/perspective/fonts/Roboto-Light.woff");
}

@font-face {
    font-family: "Roboto";
    font-weight: 700;
    src: url("/data/perspective/fonts/Roboto-Bold.woff");
}

@font-face {
    font-family: "Roboto-italic";
    font-style: italic;
    src: url("/data/perspective/fonts/Roboto-Italic.woff");
}

@font-face {
    font-family: "Roboto-mono";
    src: url("/data/perspective/fonts/RobotoMono-Regular.woff");
}

@font-face {
    font-family: "Roboto-condensed";
    src: url("/data/perspective/fonts/RobotoCondensed-Regular.woff");
}

The fonts directory contains folders "Merriweather", "NotoSans" and "Roboto" with the font files in.

I am running Ignition with the official Docker image. In front of it is an Nginx reverse proxy for https.


To circumvent this issue, I have also tried to add the custom css files following the instructions from this Ignition Exchange: Ignition Exchange | Inductive Automation
But instead of creating a whole new theme file as well, I imported the new files into the light.css file so that the changes would reflect in the default light theme. While I can create and use e.g. new CSS variables, the Roboto font still won't work.

You must create your own themes. The supplied themes are overwritten in a number of situations. See the readme file in the themes folder.

But the Roboto font is included in the light theme by default, is it not? So I don't understand why I'm getting 404 Not Founds.

To clarify even further: the NotoSans font isn't working either. So the problem isn't my custom theme attempt to fix this issue, but the default fonts not working at all.

Also, my attempt at circumventing the issue also followed the instructions from that README:

Overriding or Adapting Themes

Overriding or adapting themes is easy by leveraging the "C" in CSS (Cascading Style Sheet). Simply add your own CSS import pointing to your own custom style sheet containing the rulesets that you want to override or adapt AFTER any existing imports. For example, to override something declared in IA's owned light theme, add your own import in the light.css entry file like so:

light.css

@import "./light/index.css" 
@import "./custom/overrides.css"

We do not attempt to rewrite this file on startup unless it does not exist.

It even states that it does not rewrite the light.css file. (The CSS files inside of the light directory however, will get rewritten on startup).


After restarting my gateway docker once again, I noticed the following error coming from the AbstractAssetManager:

Unable to scan directory for files
java.lang.IllegalStateException: Duplicate key [B@1e1a91b6
at com.inductiveautomation.perspective.gateway.assets.AbstractAssetManager.lambda$scanDirForFiles$8(AbstractAssetManager.java:381)
at java.base/java.util.HashMap.merge(Unknown Source)
at java.base/java.util.stream.Collectors.lambda$toMap$68(Unknown Source)
at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(Unknown Source)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(Unknown Source)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(Unknown Source)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(Unknown Source)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(Unknown Source)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(Unknown Source)
at java.base/java.util.Iterator.forEachRemaining(Unknown Source)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Unknown Source)
at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(Unknown Source)
at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source)
at java.base/java.util.stream.ReferencePipeline.collect(Unknown Source)
at com.inductiveautomation.perspective.gateway.assets.AbstractAssetManager.scanDirForFiles(AbstractAssetManager.java:377)
at com.inductiveautomation.perspective.gateway.assets.AbstractAssetManager.onWatchEvent(AbstractAssetManager.java:147)
at com.inductiveautomation.perspective.gateway.files.FileWatcher.checkIfPathToWatchIsReady(FileWatcher.java:152)
at com.inductiveautomation.perspective.gateway.files.FileWatcher.onStartup(FileWatcher.java:94)
at com.inductiveautomation.ignition.common.lifecycle.AbstractLifecycle.startup(AbstractLifecycle.java:18)
at com.inductiveautomation.perspective.gateway.assets.AbstractAssetManager.onStartup(AbstractAssetManager.java:345)
at com.inductiveautomation.ignition.common.lifecycle.AbstractLifecycle.startup(AbstractLifecycle.java:18)
at com.inductiveautomation.perspective.gateway.GatewayHook.startup(GatewayHook.java:224)
at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl$LoadedModule.startup(ModuleManagerImpl.java:2439)
at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.startup(ModuleManagerImpl.java:417)
at com.inductiveautomation.ignition.gateway.IgnitionGateway.startupInternal(IgnitionGateway.java:1327)
at com.inductiveautomation.ignition.gateway.redundancy.RedundancyManagerImpl.startup(RedundancyManagerImpl.java:301)
at com.inductiveautomation.ignition.gateway.IgnitionGateway.initRedundancy(IgnitionGateway.java:751)
at com.inductiveautomation.ignition.gateway.IgnitionGateway.lambda$initInternal$0(IgnitionGateway.java:685)
at com.inductiveautomation.ignition.common.execution.impl.BasicExecutionEngine$ThrowableCatchingRunnable.run(BasicExecutionEngine.java:544)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)

I have no idea where the "[B@1e1a91b6" string comes from or where I should start looking for this duplicate key, or if this is even related to this font issue...

Sooo the fonts directory also contained a couple of Roboto font files, basically a subset from the ones in the fonts/Roboto folder. I guess this is what led to the AbstractAssetManager duplicate key error, because removing those files seems to have fixed the 404s and I don't see that error popping up anymore. Hope it stays working!