-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfont_manager.py
More file actions
174 lines (147 loc) · 5.81 KB
/
font_manager.py
File metadata and controls
174 lines (147 loc) · 5.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#!/usr/bin/env python3
"""
Font Manager for Python GUI Menu Application
Handles font detection and provides robust cross-platform font fallbacks
"""
import os
import sys
import platform
from PyQt5.QtGui import QFontDatabase, QFont
from PyQt5.QtCore import QStandardPaths
class FontManager:
"""Manages font detection and provides cross-platform font fallbacks"""
def __init__(self):
self.font_db = QFontDatabase()
self.loaded_fonts = {}
# Platform-specific monospace font stacks
self.platform_fonts = {
'Windows': [
"Consolas",
"Lucida Console",
"Courier New",
"monospace"
],
'Darwin': [ # macOS
"SF Mono",
"Monaco",
"Menlo",
"Courier New",
"monospace"
],
'Linux': [
"DejaVu Sans Mono",
"Liberation Mono",
"Ubuntu Mono",
"Noto Sans Mono",
"Droid Sans Mono",
"Courier New",
"monospace"
]
}
# Universal fallback stack
self.universal_monospace_stack = [
"DejaVu Sans Mono",
"Liberation Mono",
"Consolas",
"SF Mono",
"Monaco",
"Menlo",
"Ubuntu Mono",
"Noto Sans Mono",
"Droid Sans Mono",
"Lucida Console",
"Courier New",
"Courier",
"monospace",
"serif" # Last resort
]
# Get platform-specific fonts first, then universal
current_platform = platform.system()
platform_fonts = self.platform_fonts.get(current_platform, [])
# Combine platform-specific + universal (removing duplicates)
self.monospace_font_stack = []
seen = set()
for font in platform_fonts + self.universal_monospace_stack:
if font not in seen:
self.monospace_font_stack.append(font)
seen.add(font)
def get_resource_path(self, relative_path):
"""Get the absolute path to a resource file"""
if hasattr(sys, '_MEIPASS'):
# Running from PyInstaller bundle
return os.path.join(sys._MEIPASS, relative_path)
else:
# Running from source
return os.path.join(os.path.dirname(os.path.abspath(__file__)), relative_path)
def detect_best_monospace_font(self):
"""Detect the best available monospace font on this system"""
# Check available fonts and find the best match
available_families = set(self.font_db.families())
for font_family in self.monospace_font_stack:
if font_family in available_families:
# Verify it's actually monospace
font = QFont(font_family)
if font.fixedPitch() or self._is_likely_monospace(font_family):
return font_family
# If no specific font found, return the first available from our stack
for font_family in self.monospace_font_stack:
if font_family in available_families:
return font_family
return "Courier" # Ultimate fallback
def _is_likely_monospace(self, font_family):
"""Check if a font family is likely to be monospace"""
monospace_indicators = [
'mono', 'console', 'terminal', 'fixed', 'typewriter', 'courier'
]
font_lower = font_family.lower()
return any(indicator in font_lower for indicator in monospace_indicators)
def get_monospace_font(self, size=10, weight=QFont.Normal):
"""Get the best available monospace font"""
best_family = self.detect_best_monospace_font()
font = QFont(best_family, size, weight)
# Ensure it behaves as monospace
font.setStyleHint(QFont.TypeWriter)
font.setFixedPitch(True)
return font
def get_monospace_font_css(self, size=None):
"""Get CSS font-family stack for monospace fonts"""
css_stack = []
available_families = set(self.font_db.families())
# Only add fonts that actually exist on this system
for family in self.monospace_font_stack:
if family in available_families:
# Escape font names with spaces
if ' ' in family:
css_stack.append(f'"{family}"')
else:
css_stack.append(family)
# If no specific fonts were found, add some safe defaults
if not css_stack:
css_stack.extend(['Courier New', 'Courier'])
# Add generic fallbacks
css_stack.append('monospace')
font_family = f"font-family: {', '.join(css_stack)};"
if size:
return f"{font_family} font-size: {size}px;"
return font_family
def list_available_fonts(self):
"""List all available fonts for debugging"""
families = self.font_db.families()
monospace_families = []
for family in families:
font = QFont(family)
if font.fixedPitch() or 'mono' in family.lower() or family in self.monospace_font_stack:
monospace_families.append(family)
return {
'all_families': sorted(families),
'monospace_families': sorted(monospace_families),
'loaded_fonts': self.loaded_fonts
}
# Global font manager instance
_font_manager = None
def get_font_manager():
"""Get the global font manager instance"""
global _font_manager
if _font_manager is None:
_font_manager = FontManager()
return _font_manager