diff --git a/app/core/src/main/resources/static/fonts/DejaVuSans-Bold.ttf b/app/core/src/main/resources/static/fonts/DejaVuSans-Bold.ttf new file mode 100644 index 000000000..6d65fa7dc Binary files /dev/null and b/app/core/src/main/resources/static/fonts/DejaVuSans-Bold.ttf differ diff --git a/app/core/src/main/resources/static/fonts/DejaVuSans-BoldOblique.ttf b/app/core/src/main/resources/static/fonts/DejaVuSans-BoldOblique.ttf new file mode 100644 index 000000000..753f2d80b Binary files /dev/null and b/app/core/src/main/resources/static/fonts/DejaVuSans-BoldOblique.ttf differ diff --git a/app/core/src/main/resources/static/fonts/DejaVuSans-Oblique.ttf b/app/core/src/main/resources/static/fonts/DejaVuSans-Oblique.ttf new file mode 100644 index 000000000..999bac771 Binary files /dev/null and b/app/core/src/main/resources/static/fonts/DejaVuSans-Oblique.ttf differ diff --git a/app/core/src/main/resources/static/fonts/DejaVuSans.ttf b/app/core/src/main/resources/static/fonts/DejaVuSans.ttf new file mode 100644 index 000000000..e5f7eecce Binary files /dev/null and b/app/core/src/main/resources/static/fonts/DejaVuSans.ttf differ diff --git a/app/core/src/main/resources/static/fonts/DejaVuSansMono-Bold.ttf b/app/core/src/main/resources/static/fonts/DejaVuSansMono-Bold.ttf new file mode 100644 index 000000000..8184ced8c Binary files /dev/null and b/app/core/src/main/resources/static/fonts/DejaVuSansMono-Bold.ttf differ diff --git a/app/core/src/main/resources/static/fonts/DejaVuSansMono-BoldOblique.ttf b/app/core/src/main/resources/static/fonts/DejaVuSansMono-BoldOblique.ttf new file mode 100644 index 000000000..754dca732 Binary files /dev/null and b/app/core/src/main/resources/static/fonts/DejaVuSansMono-BoldOblique.ttf differ diff --git a/app/core/src/main/resources/static/fonts/DejaVuSansMono-Oblique.ttf b/app/core/src/main/resources/static/fonts/DejaVuSansMono-Oblique.ttf new file mode 100644 index 000000000..4c858d401 Binary files /dev/null and b/app/core/src/main/resources/static/fonts/DejaVuSansMono-Oblique.ttf differ diff --git a/app/core/src/main/resources/static/fonts/DejaVuSansMono.ttf b/app/core/src/main/resources/static/fonts/DejaVuSansMono.ttf new file mode 100644 index 000000000..f5786022f Binary files /dev/null and b/app/core/src/main/resources/static/fonts/DejaVuSansMono.ttf differ diff --git a/app/core/src/main/resources/static/fonts/DejaVuSerif-Bold.ttf b/app/core/src/main/resources/static/fonts/DejaVuSerif-Bold.ttf new file mode 100644 index 000000000..3bb755fa1 Binary files /dev/null and b/app/core/src/main/resources/static/fonts/DejaVuSerif-Bold.ttf differ diff --git a/app/core/src/main/resources/static/fonts/DejaVuSerif-BoldItalic.ttf b/app/core/src/main/resources/static/fonts/DejaVuSerif-BoldItalic.ttf new file mode 100644 index 000000000..a36dd4b70 Binary files /dev/null and b/app/core/src/main/resources/static/fonts/DejaVuSerif-BoldItalic.ttf differ diff --git a/app/core/src/main/resources/static/fonts/DejaVuSerif-Italic.ttf b/app/core/src/main/resources/static/fonts/DejaVuSerif-Italic.ttf new file mode 100644 index 000000000..805daf222 Binary files /dev/null and b/app/core/src/main/resources/static/fonts/DejaVuSerif-Italic.ttf differ diff --git a/app/core/src/main/resources/static/fonts/DejaVuSerif.ttf b/app/core/src/main/resources/static/fonts/DejaVuSerif.ttf new file mode 100644 index 000000000..0b803d206 Binary files /dev/null and b/app/core/src/main/resources/static/fonts/DejaVuSerif.ttf differ diff --git a/app/core/src/main/resources/static/fonts/LiberationMono-Bold.ttf b/app/core/src/main/resources/static/fonts/LiberationMono-Bold.ttf new file mode 100644 index 000000000..2e46737ac Binary files /dev/null and b/app/core/src/main/resources/static/fonts/LiberationMono-Bold.ttf differ diff --git a/app/core/src/main/resources/static/fonts/LiberationMono-BoldItalic.ttf b/app/core/src/main/resources/static/fonts/LiberationMono-BoldItalic.ttf new file mode 100644 index 000000000..d1f46d7cd Binary files /dev/null and b/app/core/src/main/resources/static/fonts/LiberationMono-BoldItalic.ttf differ diff --git a/app/core/src/main/resources/static/fonts/LiberationMono-Italic.ttf b/app/core/src/main/resources/static/fonts/LiberationMono-Italic.ttf new file mode 100644 index 000000000..954c39436 Binary files /dev/null and b/app/core/src/main/resources/static/fonts/LiberationMono-Italic.ttf differ diff --git a/app/core/src/main/resources/static/pdfjs-legacy/standard_fonts/LiberationMono-Regular.ttf b/app/core/src/main/resources/static/fonts/LiberationMono-Regular.ttf similarity index 100% rename from app/core/src/main/resources/static/pdfjs-legacy/standard_fonts/LiberationMono-Regular.ttf rename to app/core/src/main/resources/static/fonts/LiberationMono-Regular.ttf diff --git a/app/core/src/main/resources/static/pdfjs-legacy/standard_fonts/LiberationSans-Bold.ttf b/app/core/src/main/resources/static/fonts/LiberationSans-Bold.ttf similarity index 100% rename from app/core/src/main/resources/static/pdfjs-legacy/standard_fonts/LiberationSans-Bold.ttf rename to app/core/src/main/resources/static/fonts/LiberationSans-Bold.ttf diff --git a/app/core/src/main/resources/static/pdfjs-legacy/standard_fonts/LiberationSans-BoldItalic.ttf b/app/core/src/main/resources/static/fonts/LiberationSans-BoldItalic.ttf similarity index 100% rename from app/core/src/main/resources/static/pdfjs-legacy/standard_fonts/LiberationSans-BoldItalic.ttf rename to app/core/src/main/resources/static/fonts/LiberationSans-BoldItalic.ttf diff --git a/app/core/src/main/resources/static/pdfjs-legacy/standard_fonts/LiberationSans-Italic.ttf b/app/core/src/main/resources/static/fonts/LiberationSans-Italic.ttf similarity index 100% rename from app/core/src/main/resources/static/pdfjs-legacy/standard_fonts/LiberationSans-Italic.ttf rename to app/core/src/main/resources/static/fonts/LiberationSans-Italic.ttf diff --git a/app/core/src/main/resources/static/pdfjs-legacy/standard_fonts/LiberationSans-Regular.ttf b/app/core/src/main/resources/static/fonts/LiberationSans-Regular.ttf similarity index 100% rename from app/core/src/main/resources/static/pdfjs-legacy/standard_fonts/LiberationSans-Regular.ttf rename to app/core/src/main/resources/static/fonts/LiberationSans-Regular.ttf diff --git a/app/core/src/main/resources/static/fonts/LiberationSerif-Bold.ttf b/app/core/src/main/resources/static/fonts/LiberationSerif-Bold.ttf new file mode 100644 index 000000000..3c7c55b57 Binary files /dev/null and b/app/core/src/main/resources/static/fonts/LiberationSerif-Bold.ttf differ diff --git a/app/core/src/main/resources/static/fonts/LiberationSerif-BoldItalic.ttf b/app/core/src/main/resources/static/fonts/LiberationSerif-BoldItalic.ttf new file mode 100644 index 000000000..6b35d9f7c Binary files /dev/null and b/app/core/src/main/resources/static/fonts/LiberationSerif-BoldItalic.ttf differ diff --git a/app/core/src/main/resources/static/fonts/LiberationSerif-Italic.ttf b/app/core/src/main/resources/static/fonts/LiberationSerif-Italic.ttf new file mode 100644 index 000000000..54d516481 Binary files /dev/null and b/app/core/src/main/resources/static/fonts/LiberationSerif-Italic.ttf differ diff --git a/app/core/src/main/resources/static/pdfjs-legacy/standard_fonts/LiberationSerif-Regular.ttf b/app/core/src/main/resources/static/fonts/LiberationSerif-Regular.ttf similarity index 100% rename from app/core/src/main/resources/static/pdfjs-legacy/standard_fonts/LiberationSerif-Regular.ttf rename to app/core/src/main/resources/static/fonts/LiberationSerif-Regular.ttf diff --git a/app/core/src/main/resources/static/fonts/NotoSans-Bold.ttf b/app/core/src/main/resources/static/fonts/NotoSans-Bold.ttf new file mode 100644 index 000000000..21fbbcc6d Binary files /dev/null and b/app/core/src/main/resources/static/fonts/NotoSans-Bold.ttf differ diff --git a/app/core/src/main/resources/static/fonts/NotoSans-BoldItalic.ttf b/app/core/src/main/resources/static/fonts/NotoSans-BoldItalic.ttf new file mode 100644 index 000000000..8faac05c2 Binary files /dev/null and b/app/core/src/main/resources/static/fonts/NotoSans-BoldItalic.ttf differ diff --git a/app/core/src/main/resources/static/fonts/NotoSans-Italic.ttf b/app/core/src/main/resources/static/fonts/NotoSans-Italic.ttf new file mode 100644 index 000000000..76c5e1a7c Binary files /dev/null and b/app/core/src/main/resources/static/fonts/NotoSans-Italic.ttf differ diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/PdfJsonFallbackFontService.java b/app/proprietary/src/main/java/stirling/software/SPDF/service/PdfJsonFallbackFontService.java index df912ce2f..38bda32a8 100644 --- a/app/proprietary/src/main/java/stirling/software/SPDF/service/PdfJsonFallbackFontService.java +++ b/app/proprietary/src/main/java/stirling/software/SPDF/service/PdfJsonFallbackFontService.java @@ -49,7 +49,16 @@ public class PdfJsonFallbackFontService { Map.entry("tinos", "fallback-liberation-serif"), Map.entry("courier", "fallback-liberation-mono"), Map.entry("couriernew", "fallback-liberation-mono"), - Map.entry("cousine", "fallback-liberation-mono")); + Map.entry("cousine", "fallback-liberation-mono"), + // DejaVu fonts - widely used open source fonts + Map.entry("dejavu", "fallback-dejavu-sans"), + Map.entry("dejavusans", "fallback-dejavu-sans"), + Map.entry("dejavuserif", "fallback-dejavu-serif"), + Map.entry("dejavumono", "fallback-dejavu-mono"), + Map.entry("dejavusansmono", "fallback-dejavu-mono"), + // Noto Sans - Google's universal font (use as last resort generic fallback) + Map.entry("noto", "fallback-noto-sans"), + Map.entry("notosans", "fallback-noto-sans")); private static final Map BUILT_IN_FALLBACK_FONTS = Map.ofEntries( @@ -85,40 +94,172 @@ public class PdfJsonFallbackFontService { Map.entry( "fallback-liberation-sans", new FallbackFontSpec( - "classpath:/static/pdfjs-legacy/standard_fonts/LiberationSans-Regular.ttf", + "classpath:/static/fonts/LiberationSans-Regular.ttf", "LiberationSans-Regular", "ttf")), Map.entry( "fallback-liberation-sans-bold", new FallbackFontSpec( - "classpath:/static/pdfjs-legacy/standard_fonts/LiberationSans-Bold.ttf", + "classpath:/static/fonts/LiberationSans-Bold.ttf", "LiberationSans-Bold", "ttf")), Map.entry( "fallback-liberation-sans-italic", new FallbackFontSpec( - "classpath:/static/pdfjs-legacy/standard_fonts/LiberationSans-Italic.ttf", + "classpath:/static/fonts/LiberationSans-Italic.ttf", "LiberationSans-Italic", "ttf")), Map.entry( "fallback-liberation-sans-bolditalic", new FallbackFontSpec( - "classpath:/static/pdfjs-legacy/standard_fonts/LiberationSans-BoldItalic.ttf", + "classpath:/static/fonts/LiberationSans-BoldItalic.ttf", "LiberationSans-BoldItalic", "ttf")), // Liberation Serif family Map.entry( "fallback-liberation-serif", new FallbackFontSpec( - "classpath:/static/pdfjs-legacy/standard_fonts/LiberationSerif-Regular.ttf", + "classpath:/static/fonts/LiberationSerif-Regular.ttf", "LiberationSerif-Regular", "ttf")), + Map.entry( + "fallback-liberation-serif-bold", + new FallbackFontSpec( + "classpath:/static/fonts/LiberationSerif-Bold.ttf", + "LiberationSerif-Bold", + "ttf")), + Map.entry( + "fallback-liberation-serif-italic", + new FallbackFontSpec( + "classpath:/static/fonts/LiberationSerif-Italic.ttf", + "LiberationSerif-Italic", + "ttf")), + Map.entry( + "fallback-liberation-serif-bolditalic", + new FallbackFontSpec( + "classpath:/static/fonts/LiberationSerif-BoldItalic.ttf", + "LiberationSerif-BoldItalic", + "ttf")), // Liberation Mono family Map.entry( "fallback-liberation-mono", new FallbackFontSpec( - "classpath:/static/pdfjs-legacy/standard_fonts/LiberationMono-Regular.ttf", + "classpath:/static/fonts/LiberationMono-Regular.ttf", "LiberationMono-Regular", + "ttf")), + Map.entry( + "fallback-liberation-mono-bold", + new FallbackFontSpec( + "classpath:/static/fonts/LiberationMono-Bold.ttf", + "LiberationMono-Bold", + "ttf")), + Map.entry( + "fallback-liberation-mono-italic", + new FallbackFontSpec( + "classpath:/static/fonts/LiberationMono-Italic.ttf", + "LiberationMono-Italic", + "ttf")), + Map.entry( + "fallback-liberation-mono-bolditalic", + new FallbackFontSpec( + "classpath:/static/fonts/LiberationMono-BoldItalic.ttf", + "LiberationMono-BoldItalic", + "ttf")), + // Noto Sans family (enhanced with weight variants) + Map.entry( + FALLBACK_FONT_ID, + new FallbackFontSpec( + DEFAULT_FALLBACK_FONT_LOCATION, "NotoSans-Regular", "ttf")), + Map.entry( + "fallback-noto-sans-bold", + new FallbackFontSpec( + "classpath:/static/fonts/NotoSans-Bold.ttf", + "NotoSans-Bold", + "ttf")), + Map.entry( + "fallback-noto-sans-italic", + new FallbackFontSpec( + "classpath:/static/fonts/NotoSans-Italic.ttf", + "NotoSans-Italic", + "ttf")), + Map.entry( + "fallback-noto-sans-bolditalic", + new FallbackFontSpec( + "classpath:/static/fonts/NotoSans-BoldItalic.ttf", + "NotoSans-BoldItalic", + "ttf")), + // DejaVu Sans family + Map.entry( + "fallback-dejavu-sans", + new FallbackFontSpec( + "classpath:/static/fonts/DejaVuSans.ttf", "DejaVuSans", "ttf")), + Map.entry( + "fallback-dejavu-sans-bold", + new FallbackFontSpec( + "classpath:/static/fonts/DejaVuSans-Bold.ttf", + "DejaVuSans-Bold", + "ttf")), + Map.entry( + "fallback-dejavu-sans-oblique", + new FallbackFontSpec( + "classpath:/static/fonts/DejaVuSans-Oblique.ttf", + "DejaVuSans-Oblique", + "ttf")), + Map.entry( + "fallback-dejavu-sans-boldoblique", + new FallbackFontSpec( + "classpath:/static/fonts/DejaVuSans-BoldOblique.ttf", + "DejaVuSans-BoldOblique", + "ttf")), + // DejaVu Serif family + Map.entry( + "fallback-dejavu-serif", + new FallbackFontSpec( + "classpath:/static/fonts/DejaVuSerif.ttf", + "DejaVuSerif", + "ttf")), + Map.entry( + "fallback-dejavu-serif-bold", + new FallbackFontSpec( + "classpath:/static/fonts/DejaVuSerif-Bold.ttf", + "DejaVuSerif-Bold", + "ttf")), + Map.entry( + "fallback-dejavu-serif-italic", + new FallbackFontSpec( + "classpath:/static/fonts/DejaVuSerif-Italic.ttf", + "DejaVuSerif-Italic", + "ttf")), + Map.entry( + "fallback-dejavu-serif-bolditalic", + new FallbackFontSpec( + "classpath:/static/fonts/DejaVuSerif-BoldItalic.ttf", + "DejaVuSerif-BoldItalic", + "ttf")), + // DejaVu Mono family + Map.entry( + "fallback-dejavu-mono", + new FallbackFontSpec( + "classpath:/static/fonts/DejaVuSansMono.ttf", + "DejaVuSansMono", + "ttf")), + Map.entry( + "fallback-dejavu-mono-bold", + new FallbackFontSpec( + "classpath:/static/fonts/DejaVuSansMono-Bold.ttf", + "DejaVuSansMono-Bold", + "ttf")), + Map.entry( + "fallback-dejavu-mono-oblique", + new FallbackFontSpec( + "classpath:/static/fonts/DejaVuSansMono-Oblique.ttf", + "DejaVuSansMono-Oblique", + "ttf")), + Map.entry( + "fallback-dejavu-mono-boldoblique", + new FallbackFontSpec( + "classpath:/static/fonts/DejaVuSansMono-BoldOblique.ttf", + "DejaVuSansMono-BoldOblique", "ttf"))); private final ResourceLoader resourceLoader; @@ -198,7 +339,7 @@ public class PdfJsonFallbackFontService { /** * Resolve fallback font ID based on the original font name and code point. Attempts to match - * font family for visual consistency. + * font family and weight/style for visual consistency. * * @param originalFontName the name of the original font (may be null) * @param codePoint the Unicode code point that needs to be rendered @@ -223,13 +364,22 @@ public class PdfJsonFallbackFontService { String aliasedFontId = FONT_NAME_ALIASES.get(baseName); if (aliasedFontId != null) { + // Detect weight and style from the normalized font name + boolean isBold = detectBold(normalized); + boolean isItalic = detectItalic(normalized); + + // Apply weight/style suffix to fallback font ID + String styledFontId = applyWeightStyle(aliasedFontId, isBold, isItalic); + log.debug( - "Matched font '{}' (normalized: '{}', base: '{}') to fallback '{}'", + "Matched font '{}' (normalized: '{}', base: '{}', bold: {}, italic: {}) to fallback '{}'", originalFontName, normalized, baseName, - aliasedFontId); - return aliasedFontId; + isBold, + isItalic, + styledFontId); + return styledFontId; } } @@ -237,6 +387,89 @@ public class PdfJsonFallbackFontService { return resolveFallbackFontId(codePoint); } + /** + * Detect if font name indicates bold weight. + * + * @param normalizedFontName lowercase font name without subset prefix or spaces + * @return true if bold weight is detected + */ + private boolean detectBold(String normalizedFontName) { + // Check for explicit bold indicators + if (normalizedFontName.contains("bold") + || normalizedFontName.contains("heavy") + || normalizedFontName.contains("black")) { + return true; + } + + // Check for numeric weight indicators (600-900 = bold) + // Handles: "Arimo_700wght", "Arial-700", "Font-w700" + if (normalizedFontName.matches(".*[_-]?[6-9]00(wght)?.*")) { + return true; + } + + return false; + } + + /** + * Detect if font name indicates italic/oblique style. + * + * @param normalizedFontName lowercase font name without subset prefix or spaces + * @return true if italic style is detected + */ + private boolean detectItalic(String normalizedFontName) { + return normalizedFontName.contains("italic") || normalizedFontName.contains("oblique"); + } + + /** + * Apply weight/style suffix to fallback font ID. + * + *

Weight/style variants are only applied to font families where we have the actual font + * files available. Currently supported: - Liberation Sans: Regular, Bold, Italic, BoldItalic + * (full support) - Liberation Serif: Regular, Bold, Italic, BoldItalic (full support) - + * Liberation Mono: Regular, Bold, Italic, BoldItalic (full support) - Noto Sans: Regular, Bold, + * Italic, BoldItalic (full support) - DejaVu Sans: Regular, Bold, Oblique, BoldOblique (full + * support) - DejaVu Serif: Regular, Bold, Italic, BoldItalic (full support) - DejaVu Mono: + * Regular, Bold, Oblique, BoldOblique (full support) + * + *

To add weight/style support for additional font families: 1. Download the font files + * (Bold, Italic, BoldItalic) to: app/core/src/main/resources/static/fonts/ 2. Register the + * variants in BUILT_IN_FALLBACK_FONTS map (see lines 63-267) 3. Update the check below to + * include the font family prefix + * + * @param baseFontId base fallback font ID (e.g., "fallback-liberation-sans") + * @param isBold true if bold weight needed + * @param isItalic true if italic style needed + * @return styled font ID (e.g., "fallback-liberation-sans-bold"), or base ID if variants not + * available + */ + private String applyWeightStyle(String baseFontId, boolean isBold, boolean isItalic) { + // Only apply weight/style to font families where we have the font files available + // Supported: Liberation (Sans/Serif/Mono), Noto Sans, DejaVu (Sans/Serif/Mono) + boolean isSupported = + baseFontId.startsWith("fallback-liberation-") + || baseFontId.equals("fallback-noto-sans") + || baseFontId.startsWith("fallback-dejavu-"); + + if (!isSupported) { + return baseFontId; + } + + // DejaVu Sans and Mono use "oblique" instead of "italic" + boolean useOblique = + baseFontId.equals("fallback-dejavu-sans") + || baseFontId.equals("fallback-dejavu-mono"); + + if (isBold && isItalic) { + return baseFontId + (useOblique ? "-boldoblique" : "-bolditalic"); + } else if (isBold) { + return baseFontId + "-bold"; + } else if (isItalic) { + return baseFontId + (useOblique ? "-oblique" : "-italic"); + } + + return baseFontId; + } + /** * Resolve fallback font ID based on Unicode code point properties. * diff --git a/docs/pdf-json-editor-backlog.md b/docs/pdf-json-editor-backlog.md index 35b604295..a9bbe8f27 100644 --- a/docs/pdf-json-editor-backlog.md +++ b/docs/pdf-json-editor-backlog.md @@ -23,15 +23,19 @@ - Update `buildFontMap` to resolve aliases when recreating PDFBox fonts, and adjust the front end to load programs via the canonical UID. - Optional: expose a lazy endpoint for the original COS dictionary if the canonical record strips it, so export still reconstructs untouched fonts. -- **Font Weight Matching for Fallback Fonts** - - Font family matching is now implemented (Arial→LiberationSans, Times→LiberationSerif, Courier→LiberationMono). - - However, fallback fonts still use Regular weight for all missing glyphs, regardless of the original font weight (e.g., bold text falls back to regular weight). - - TODO: Parse weight from font names (e.g., `Arimo_700wght`, `Arial-Bold`, `TimesNewRoman,SemiBold`) and map to corresponding Liberation font variants: - - Regular/Normal → LiberationSans-Regular, LiberationSerif-Regular, LiberationMono-Regular - - Bold/700 → LiberationSans-Bold, LiberationSerif-Bold, LiberationMono-Bold - - Italic/Oblique → LiberationSans-Italic, LiberationSerif-Italic, LiberationMono-Italic - - BoldItalic → LiberationSans-BoldItalic, LiberationSerif-BoldItalic, LiberationMono-BoldItalic - - Add all Liberation font variants to `BUILT_IN_FALLBACK_FONTS` map with appropriate IDs (e.g., `fallback-liberation-sans-bold`). - - Update `resolveFallbackFontId(String originalFontName, int codePoint)` in `PdfJsonFallbackFontService.java` to detect weight/style and return the matching variant ID. - - Benefits: Better visual consistency when editing text in bold/italic fonts, as missing characters will match the original weight. - - Implementation reference: `app/proprietary/src/main/java/stirling/software/SPDF/service/PdfJsonFallbackFontService.java:186-213` +- **Font Weight Matching for Fallback Fonts** ✓ COMPLETED (January 2025) + - Font family matching is now implemented: + - Liberation fonts (metric-compatible with Microsoft core): Arial/Helvetica→LiberationSans, Times→LiberationSerif, Courier→LiberationMono + - DejaVu fonts (widely used open source): DejaVu→DejaVuSans, DejaVuSerif, DejaVuMono + - Noto fonts (Google universal font): Noto→NotoSans + - Font weight/style matching is now implemented for multiple font families: + - Liberation Sans/Serif/Mono: Regular, Bold, Italic, BoldItalic (full support) + - Noto Sans: Regular, Bold, Italic, BoldItalic (full support) + - DejaVu Sans/Serif/Mono: Regular, Bold, Italic/Oblique, BoldItalic/BoldOblique (full support) + - All font variants registered in `BUILT_IN_FALLBACK_FONTS` map (`PdfJsonFallbackFontService.java:63-267`) + - Weight/style detection implemented in `resolveFallbackFontId()`: + - `detectBold()`: Detects "bold", "heavy", "black", or numeric weights 600-900 (e.g., "700wght") + - `detectItalic()`: Detects "italic" or "oblique" + - `applyWeightStyle()`: Applies appropriate suffix (handles both "italic" and "oblique" naming) + - All fonts consolidated from Type3 library into main fonts directory for unified fallback support + - Benefits: Comprehensive visual consistency when editing text in bold/italic fonts across many font families