diff --git a/scripts/woff2/assets/LiberationSans-Regular-2048.ttf b/scripts/woff2/assets/LiberationSans-Regular-2048.ttf
new file mode 100644
index 0000000000..ff514cddee
Binary files /dev/null and b/scripts/woff2/assets/LiberationSans-Regular-2048.ttf differ
diff --git a/scripts/woff2/assets/LiberationSans-Regular.ttf b/scripts/woff2/assets/LiberationSans-Regular.ttf
new file mode 100644
index 0000000000..006f615ebe
Binary files /dev/null and b/scripts/woff2/assets/LiberationSans-Regular.ttf differ
diff --git a/scripts/woff2/woff2-esbuild-plugins.js b/scripts/woff2/woff2-esbuild-plugins.js
index bcb59ec1d2..e53dd0299e 100644
--- a/scripts/woff2/woff2-esbuild-plugins.js
+++ b/scripts/woff2/woff2-esbuild-plugins.js
@@ -123,6 +123,17 @@ module.exports.woff2ServerPlugin = (options = {}) => {
           "./assets/NotoEmoji-Regular-2048.ttf",
         );
 
+        const liberationPath = path.resolve(
+          __dirname,
+          "./assets/LiberationSans-Regular.ttf",
+        );
+
+        // need to use the same em size as built-in fonts, otherwise pyftmerge throws (modified manually with font forge)
+        const liberationPath_2048 = path.resolve(
+          __dirname,
+          "./assets/LiberationSans-Regular-2048.ttf",
+        );
+
         const xiaolaiFont = Font.create(fs.readFileSync(xiaolaiPath), {
           type: "ttf",
         });
@@ -130,6 +141,10 @@ module.exports.woff2ServerPlugin = (options = {}) => {
           type: "ttf",
         });
 
+        const liberationFont = Font.create(fs.readFileSync(liberationPath), {
+          type: "ttf",
+        });
+
         const sortedFonts = Array.from(fonts.entries()).sort(
           ([family1], [family2]) => (family1 > family2 ? 1 : -1),
         );
@@ -141,13 +156,6 @@ module.exports.woff2ServerPlugin = (options = {}) => {
             continue;
           }
 
-          const fallbackFontsPaths = [];
-          const shouldIncludeXiaolaiFallback = family.includes("Excalifont");
-
-          if (shouldIncludeXiaolaiFallback) {
-            fallbackFontsPaths.push(xiaolaiPath);
-          }
-
           const baseFont = Regular[0];
           const tempPaths = Regular.map((_, index) =>
             path.resolve(outputDir, `temp_${family}_${index}.ttf`),
@@ -165,10 +173,18 @@ module.exports.woff2ServerPlugin = (options = {}) => {
 
           const mergedFontPath = path.resolve(outputDir, `${family}.ttf`);
 
+          const fallbackFontsPaths = [];
+          const shouldIncludeXiaolaiFallback = family.includes("Excalifont");
+
+          if (shouldIncludeXiaolaiFallback) {
+            fallbackFontsPaths.push(xiaolaiPath);
+          }
+
+          // add liberation as fallback to all fonts, so that unknown characters are rendered similarly to how browser renders them (Helvetica, Arial, etc.)
           if (baseFont.data.head.unitsPerEm === 2048) {
-            fallbackFontsPaths.push(emojiPath_2048);
+            fallbackFontsPaths.push(emojiPath_2048, liberationPath_2048);
           } else {
-            fallbackFontsPaths.push(emojiPath);
+            fallbackFontsPaths.push(emojiPath, liberationPath);
           }
 
           // drop Vertical related metrics, otherwise it does not allow us to merge the fonts
@@ -196,10 +212,12 @@ module.exports.woff2ServerPlugin = (options = {}) => {
             const base = baseFont.data.name[field];
             const xiaolai = xiaolaiFont.data.name[field];
             const emoji = emojiFont.data.name[field];
+            const liberation = liberationFont.data.name[field];
+            // liberation font
 
             return shouldIncludeXiaolaiFallback
-              ? `${base} & ${xiaolai} & ${emoji}`
-              : `${base} & ${emoji}`;
+              ? `${base} & ${xiaolai} & ${emoji} & ${liberation}`
+              : `${base} & ${emoji} & ${liberation}`;
           };
 
           mergedFont.set({