mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-26 03:10:21 +00:00
Merge PR #1624: Hebrew file names: draw them with an installed Hebrew face when the build has none
This commit is contained in:
@@ -3,10 +3,10 @@
|
||||
//! The embedded faces (Inter, egui's defaults, craft-fonts) come first in every family; this one
|
||||
//! only draws characters none of them has, such as an Arabic file name in a build without
|
||||
//! craft-fonts, or the Simplified Chinese interface in a build without a `Hans` face (#826). A
|
||||
//! few more installed faces, [`devanagari_fallback`], [`telugu_fallback`], [`sinhala_fallback`]
|
||||
//! and [`tamil_fallback`], draw Hindi, Telugu, Sinhala and Tamil when one is installed. It is
|
||||
//! read at runtime and never embedded or shipped (AGENTS.md §1.4), and `PDFCRAFT_SYSTEM_FONTS=0`
|
||||
//! turns it off (published screenshots do).
|
||||
//! few more installed faces, [`devanagari_fallback`], [`telugu_fallback`], [`sinhala_fallback`],
|
||||
//! [`tamil_fallback`] and [`hebrew_fallback`], draw Hindi, Telugu, Sinhala, Tamil and Hebrew when
|
||||
//! one is installed. It is read at runtime and never embedded or shipped (AGENTS.md §1.4), and
|
||||
//! `PDFCRAFT_SYSTEM_FONTS=0` turns it off (published screenshots do).
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
@@ -32,6 +32,8 @@ const TELUGU_PROBE: char = '\u{0C15}';
|
||||
const SINHALA_PROBE: char = '\u{0D9A}';
|
||||
/// Tamil letter ka (க): the face must have it to cover Tamil.
|
||||
const TAMIL_PROBE: char = '\u{0B95}';
|
||||
/// Hebrew letter alef: the face must have it to cover Hebrew.
|
||||
const HEBREW_PROBE: char = '\u{05D0}';
|
||||
/// 欢 (U+6B22): Simplified only, absent from the Japanese craft-fonts faces.
|
||||
const HANS_PROBE: char = '\u{6B22}';
|
||||
/// 說 (U+8AAA): Traditional only, absent from the Japanese craft-fonts faces.
|
||||
@@ -103,6 +105,13 @@ pub fn tamil_fallback() -> Option<Arc<FontData>> {
|
||||
CACHE.get_or_init(|| script_fallback(&tamil_candidates(), TAMIL_PROBE)).clone()
|
||||
}
|
||||
|
||||
/// The installed fallback face for Hebrew, read once. `None` when it is turned off or no
|
||||
/// candidate is installed; the interface then draws what the embedded faces can.
|
||||
pub fn hebrew_fallback() -> Option<Arc<FontData>> {
|
||||
static CACHE: OnceLock<Option<Arc<FontData>>> = OnceLock::new();
|
||||
CACHE.get_or_init(|| script_fallback(&hebrew_candidates(), HEBREW_PROBE)).clone()
|
||||
}
|
||||
|
||||
fn script_fallback(candidates: &[PathBuf], probe: char) -> Option<Arc<FontData>> {
|
||||
if std::env::var_os("PDFCRAFT_SYSTEM_FONTS").is_some_and(|v| v == "0") {
|
||||
return None;
|
||||
@@ -227,6 +236,30 @@ fn tamil_candidates() -> Vec<PathBuf> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Well-known locations of Hebrew faces, best first. The general fallback does not cover it:
|
||||
/// SF Arabic, Geeza Pro and Noto Sans Arabic have no Hebrew.
|
||||
fn hebrew_candidates() -> Vec<PathBuf> {
|
||||
if cfg!(windows) {
|
||||
["segoeui.ttf", "arial.ttf", "tahoma.ttf", "david.ttf"].iter().map(|f| windows_fonts().join(f)).collect()
|
||||
} else if cfg!(target_os = "macos") {
|
||||
["/System/Library/Fonts/SFHebrew.ttf", "/System/Library/Fonts/ArialHB.ttc", "/Library/Fonts/Arial Unicode.ttf"]
|
||||
.iter()
|
||||
.map(PathBuf::from)
|
||||
.collect()
|
||||
} else {
|
||||
unix_fonts(&[
|
||||
"google-noto/NotoSansHebrew-Regular.ttf",
|
||||
"google-noto-vf/NotoSansHebrew[wght].ttf",
|
||||
"truetype/noto/NotoSansHebrew-Regular.ttf",
|
||||
"noto/NotoSansHebrew-Regular.ttf",
|
||||
"truetype/dejavu/DejaVuSans.ttf",
|
||||
"TTF/DejaVuSans.ttf",
|
||||
"dejavu/DejaVuSans.ttf",
|
||||
"dejavu-sans-fonts/DejaVuSans.ttf",
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/// Well-known locations of faces with broad script coverage, best first.
|
||||
fn candidates(need: Need) -> Vec<PathBuf> {
|
||||
match need {
|
||||
@@ -407,11 +440,12 @@ mod tests {
|
||||
assert!(script_fallback(&candidates, DEVANAGARI_PROBE).is_none());
|
||||
assert!(script_fallback(&candidates, SINHALA_PROBE).is_none());
|
||||
assert!(script_fallback(&candidates, TAMIL_PROBE).is_none());
|
||||
assert!(script_fallback(&candidates, HEBREW_PROBE).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_candidates_are_absolute_font_files() {
|
||||
for list in [devanagari_candidates(), telugu_candidates(), sinhala_candidates(), tamil_candidates()] {
|
||||
for list in [devanagari_candidates(), telugu_candidates(), sinhala_candidates(), tamil_candidates(), hebrew_candidates()] {
|
||||
assert!(!list.is_empty());
|
||||
assert!(list.iter().all(|p| p.is_absolute() || cfg!(windows)), "{list:?}");
|
||||
assert!(list.iter().all(|p| p.extension().is_some_and(|e| e == "ttf" || e == "ttc")), "{list:?}");
|
||||
|
||||
@@ -224,6 +224,8 @@ pub const SYSTEM_TELUGU_FALLBACK: &str = "system-telugu-fallback";
|
||||
pub const SYSTEM_SINHALA_FALLBACK: &str = "system-sinhala-fallback";
|
||||
/// The name of the installed Tamil face [`installed_font_definitions`] may add.
|
||||
pub const SYSTEM_TAMIL_FALLBACK: &str = "system-tamil-fallback";
|
||||
/// The name of the installed Hebrew face [`installed_font_definitions`] may add.
|
||||
pub const SYSTEM_HEBREW_FALLBACK: &str = "system-hebrew-fallback";
|
||||
|
||||
/// What [`install_fonts_for`] installs: [`font_definitions_for`], then, on desktop, one face
|
||||
/// already installed on this machine. It only draws characters no embedded face has.
|
||||
@@ -236,9 +238,9 @@ pub const SYSTEM_TAMIL_FALLBACK: &str = "system-tamil-fallback";
|
||||
/// - Otherwise it is the last fallback of every family (an Arabic file name in a build without
|
||||
/// craft-fonts).
|
||||
///
|
||||
/// Installed Devanagari, Telugu, Sinhala and Tamil faces, when there are, end every family just
|
||||
/// before that one, so Hindi, Telugu (#1531), Sinhala and Tamil (#1688) text, such as a file
|
||||
/// name, draws in a build without such faces.
|
||||
/// Installed Devanagari, Telugu, Sinhala, Tamil and Hebrew faces, when there are, end every
|
||||
/// family just before that one, so Hindi, Telugu (#1531), Sinhala and Tamil (#1688) text, such as
|
||||
/// a file name, and Hebrew file names (#586) draw in a build without such faces.
|
||||
///
|
||||
/// `PDFCRAFT_SYSTEM_FONTS=0` leaves them out.
|
||||
pub fn installed_font_definitions(cjk: CjkPreference) -> FontDefinitions {
|
||||
@@ -253,6 +255,7 @@ pub fn installed_font_definitions(cjk: CjkPreference) -> FontDefinitions {
|
||||
(SYSTEM_TELUGU_FALLBACK, crate::system_fonts::telugu_fallback()),
|
||||
(SYSTEM_SINHALA_FALLBACK, crate::system_fonts::sinhala_fallback()),
|
||||
(SYSTEM_TAMIL_FALLBACK, crate::system_fonts::tamil_fallback()),
|
||||
(SYSTEM_HEBREW_FALLBACK, crate::system_fonts::hebrew_fallback()),
|
||||
] {
|
||||
let Some(data) = data else { continue };
|
||||
fonts.font_data.insert(name.to_owned(), data);
|
||||
|
||||
@@ -8,6 +8,7 @@ const JAPANESE: &str = "日本語の文字";
|
||||
const CHINESE: &str = "简体中文欢迎";
|
||||
const ARABIC: &str = "واحد اثنين";
|
||||
const TELUGU: &str = "తెలుగు ఫైల్";
|
||||
const HEBREW: &str = "ייפוי כח.pdf";
|
||||
|
||||
fn families() -> Vec<FontId> {
|
||||
vec![FontId::proportional(13.0), FontId::monospace(13.0), theme::medium(13.0), theme::semibold(17.0)]
|
||||
@@ -219,6 +220,29 @@ fn system_fallback_covers_sinhala_and_tamil() {
|
||||
}
|
||||
}
|
||||
|
||||
/// On a machine with an installed Hebrew font, the installed definitions include it and a
|
||||
/// Hebrew file name has glyphs in the document tab and the recent files (#586). macOS always
|
||||
/// ships Arial Hebrew and SF Hebrew, so there the test never skips.
|
||||
#[test]
|
||||
fn system_fallback_covers_hebrew() {
|
||||
assert!(!theme::font_definitions().font_data.contains_key(theme::SYSTEM_HEBREW_FALLBACK));
|
||||
let defs = theme::installed_font_definitions(theme::CjkPreference::Default);
|
||||
let off = std::env::var_os("PDFCRAFT_SYSTEM_FONTS").is_some_and(|v| v == "0");
|
||||
if !defs.font_data.contains_key(theme::SYSTEM_HEBREW_FALLBACK) && (off || !cfg!(target_os = "macos")) {
|
||||
eprintln!("skipping system_fallback_covers_hebrew: no installed Hebrew fallback font (or PDFCRAFT_SYSTEM_FONTS=0)");
|
||||
assert!(defs.families.values().all(|stack| !stack.iter().any(|n| n == theme::SYSTEM_HEBREW_FALLBACK)));
|
||||
return;
|
||||
}
|
||||
for (family, stack) in &defs.families {
|
||||
assert_eq!(stack.iter().filter(|n| *n == theme::SYSTEM_HEBREW_FALLBACK).count(), 1, "{family:?}: {stack:?}");
|
||||
}
|
||||
let mut fonts = Fonts::new(TextOptions::default(), defs);
|
||||
for id in families() {
|
||||
assert!(fonts.has_glyphs(&id, HEBREW), "{id:?} lacks {HEBREW}");
|
||||
}
|
||||
assert!(layout_widths(&mut fonts, HEBREW).iter().all(|w| w.is_finite() && *w > 0.0));
|
||||
}
|
||||
|
||||
/// The Han characters of a catalog's translations.
|
||||
fn catalog_han(catalog: &str) -> String {
|
||||
let mut han: Vec<char> = catalog
|
||||
|
||||
Reference in New Issue
Block a user