{"record":{"id":"e500358649926277","repo":"actualbudget/actual","slug":"total-embedded-font-data-exceeds-maximum-of-max","errorCode":null,"errorMessage":"Total embedded font data exceeds maximum of ${MAX_TOTAL_FONT_SIZE / 1024 / 1024}MB.","messagePattern":"Total embedded font data exceeds maximum of (.+?)MB\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/style/customThemes.ts","lineNumber":293,"sourceCode":"            'Font files are automatically embedded when installing from GitHub.',\n        );\n      }\n      // Estimate decoded size from base64 content\n      const base64Match = uri.match(/;base64,(.+)$/);\n      if (base64Match) {\n        const size = Math.ceil((base64Match[1].length * 3) / 4);\n        if (size > MAX_FONT_FILE_SIZE) {\n          throw new Error(\n            `Font file exceeds maximum size of ${MAX_FONT_FILE_SIZE / 1024 / 1024}MB.`,\n          );\n        }\n        totalSize += size;\n      }\n    }\n  }\n\n  if (totalSize > MAX_TOTAL_FONT_SIZE) {\n    throw new Error(\n      `Total embedded font data exceeds maximum of ${MAX_TOTAL_FONT_SIZE / 1024 / 1024}MB.`,\n    );\n  }\n}\n\n/**\n * Split CSS declarations by semicolons, but respect quoted strings and url() contents.\n * This is needed because data: URIs contain semicolons (e.g., \"data:font/woff2;base64,...\").\n */\nfunction splitDeclarations(content: string): string[] {\n  const declarations: string[] = [];\n  let start = 0;\n  let inSingleQuote = false;\n  let inDoubleQuote = false;\n  let parenDepth = 0;\n\n  for (let i = 0; i < content.length; i++) {\n    const ch = content[i];","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/style/customThemes.ts#L275-L311","documentation":"validateFontFaceBlocks sums the estimated decoded size of all embedded base64 fonts in the theme and throws if the total exceeds MAX_TOTAL_FONT_SIZE (limit in MB shown in the message). This caps the overall payload a custom theme can carry.","triggerScenarios":"Installing a theme whose actual.css embeds several @font-face data URIs whose combined decoded size exceeds MAX_TOTAL_FONT_SIZE — even if each individual font is under its own limit.","commonSituations":"Themes bundling many font weights/styles (regular, bold, italic, multiple families) all embedded as base64; combining several large CJK/variable fonts in one theme.","solutions":["Reduce the number of embedded weights/styles to only those actually used in the theme.","Subset and WOFF2-compress each embedded font to shrink the total.","Reference locally installed fonts via --font-* variables instead of embedding every family."],"exampleFix":"// before: 6 @font-face blocks (4 weights x 2 families) embedded\n// after: keep only 2 @font-face blocks (regular + bold of one family), subset to WOFF2","handlingStrategy":"validation","validationCode":"function checkTotalFontSize(css, maxBytes) {\n  let total = 0;\n  for (const m of css.matchAll(/;base64,([A-Za-z0-9+/=]+)/g)) {\n    total += Math.ceil(m[1].length * 3 / 4);\n  }\n  return total <= maxBytes;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await installTheme(css);\n} catch (err) {\n  if ((err as Error).message.includes('Total embedded font data exceeds')) {\n    // reduce embedded font count/weights in the theme\n  } else throw err;\n}","preventionTips":["Limit themes to a handful of embedded weights (regular + bold).","Budget total font size across all families when designing a theme.","Automate a size check over all data URIs in the theme build."],"tags":["css","font-face","size-limit","validation"],"backgroundTag":"payload-size-limit-exceeded","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}