Fortran · 10781 bytes Raw Blame History
1 module font_mod
2 use, intrinsic :: iso_c_binding
3 use glyph_mod
4 implicit none
5 private
6
7 public :: font_t
8 public :: font_load, font_destroy, font_render_glyph
9 public :: font_load_fallback, font_has_glyph, font_render_glyph_with_fallback
10 public :: font_find_for_codepoint, font_find_monospace
11
12 ! Font handle and metrics
13 type :: font_t
14 type(c_ptr) :: ft_library = c_null_ptr
15 type(c_ptr) :: ft_face = c_null_ptr
16 type(c_ptr) :: ft_face_fallback = c_null_ptr
17 integer :: size_px = 0
18 integer :: cell_width = 0
19 integer :: cell_height = 0
20 integer :: ascender = 0
21 integer :: descender = 0
22 integer :: fallback_ascender = 0 ! For baseline alignment with fallback font
23 logical :: loaded = .false.
24 logical :: has_fallback = .false.
25 end type font_t
26
27 ! C interface to FreeType helpers
28 interface
29 integer(c_int) function fortty_ft_init(lib) bind(C, name="fortty_ft_init")
30 import :: c_int, c_ptr
31 type(c_ptr), intent(out) :: lib
32 end function fortty_ft_init
33
34 subroutine fortty_ft_done(lib) bind(C, name="fortty_ft_done")
35 import :: c_ptr
36 type(c_ptr), value :: lib
37 end subroutine fortty_ft_done
38
39 integer(c_int) function fortty_ft_load_font(lib, path, size_px, face) &
40 bind(C, name="fortty_ft_load_font")
41 import :: c_int, c_ptr, c_char
42 type(c_ptr), value :: lib
43 character(kind=c_char), intent(in) :: path(*)
44 integer(c_int), value :: size_px
45 type(c_ptr), intent(out) :: face
46 end function fortty_ft_load_font
47
48 subroutine fortty_ft_done_face(face) bind(C, name="fortty_ft_done_face")
49 import :: c_ptr
50 type(c_ptr), value :: face
51 end subroutine fortty_ft_done_face
52
53 subroutine fortty_ft_get_metrics(face, cell_width, cell_height, &
54 ascender, descender) &
55 bind(C, name="fortty_ft_get_metrics")
56 import :: c_int, c_ptr
57 type(c_ptr), value :: face
58 integer(c_int), intent(out) :: cell_width, cell_height
59 integer(c_int), intent(out) :: ascender, descender
60 end subroutine fortty_ft_get_metrics
61
62 integer(c_int) function fortty_ft_render_glyph(face, codepoint, &
63 bitmap, width, height, bearing_x, bearing_y, advance) &
64 bind(C, name="fortty_ft_render_glyph")
65 import :: c_int, c_ptr
66 type(c_ptr), value :: face
67 integer(c_int), value :: codepoint
68 type(c_ptr), intent(out) :: bitmap
69 integer(c_int), intent(out) :: width, height
70 integer(c_int), intent(out) :: bearing_x, bearing_y, advance
71 end function fortty_ft_render_glyph
72
73 integer(c_int) function fortty_ft_has_glyph(face, codepoint) &
74 bind(C, name="fortty_ft_has_glyph")
75 import :: c_int, c_ptr
76 type(c_ptr), value :: face
77 integer(c_int), value :: codepoint
78 end function fortty_ft_has_glyph
79
80 ! Fontconfig functions for portable font discovery
81 integer(c_int) function fortty_fc_find_font_for_char(codepoint, path, path_size) &
82 bind(C, name="fortty_fc_find_font_for_char")
83 import :: c_int, c_char
84 integer(c_int), value :: codepoint
85 character(kind=c_char), intent(out) :: path(*)
86 integer(c_int), value :: path_size
87 end function fortty_fc_find_font_for_char
88
89 integer(c_int) function fortty_fc_find_monospace_font(path, path_size) &
90 bind(C, name="fortty_fc_find_monospace_font")
91 import :: c_int, c_char
92 character(kind=c_char), intent(out) :: path(*)
93 integer(c_int), value :: path_size
94 end function fortty_fc_find_monospace_font
95 end interface
96
97 contains
98
99 ! Load a font from file
100 function font_load(path, size_px) result(font)
101 character(len=*), intent(in) :: path
102 integer, intent(in) :: size_px
103 type(font_t) :: font
104 character(len=256) :: c_path
105 integer(c_int) :: err
106 integer(c_int) :: cw, ch, asc, desc
107
108 ! Initialize FreeType
109 err = fortty_ft_init(font%ft_library)
110 if (err /= 0) then
111 print *, "Error: Failed to initialize FreeType, error ", err
112 return
113 end if
114
115 ! Load font face
116 c_path = trim(path) // c_null_char
117 err = fortty_ft_load_font(font%ft_library, c_path, int(size_px, c_int), font%ft_face)
118 if (err /= 0) then
119 print *, "Error: Failed to load font '", trim(path), "', error ", err
120 call fortty_ft_done(font%ft_library)
121 font%ft_library = c_null_ptr
122 return
123 end if
124
125 ! Get metrics
126 call fortty_ft_get_metrics(font%ft_face, cw, ch, asc, desc)
127 font%size_px = size_px
128 font%cell_width = cw
129 font%cell_height = ch
130 font%ascender = asc
131 font%descender = desc
132 font%loaded = .true.
133
134 end function font_load
135
136 ! Destroy font and free resources
137 subroutine font_destroy(font)
138 type(font_t), intent(inout) :: font
139
140 if (c_associated(font%ft_face_fallback)) then
141 call fortty_ft_done_face(font%ft_face_fallback)
142 font%ft_face_fallback = c_null_ptr
143 end if
144
145 if (c_associated(font%ft_face)) then
146 call fortty_ft_done_face(font%ft_face)
147 font%ft_face = c_null_ptr
148 end if
149
150 if (c_associated(font%ft_library)) then
151 call fortty_ft_done(font%ft_library)
152 font%ft_library = c_null_ptr
153 end if
154
155 font%loaded = .false.
156 font%has_fallback = .false.
157 end subroutine font_destroy
158
159 ! Render a glyph and return its data
160 ! Returns bitmap pointer (valid until next render_glyph call)
161 function font_render_glyph(font, codepoint, bitmap_ptr) result(glyph)
162 type(font_t), intent(in) :: font
163 integer, intent(in) :: codepoint
164 type(c_ptr), intent(out) :: bitmap_ptr
165 type(glyph_t) :: glyph
166 integer(c_int) :: err, w, h, bx, by, adv
167
168 call glyph_init(glyph)
169
170 if (.not. font%loaded) then
171 bitmap_ptr = c_null_ptr
172 return
173 end if
174
175 err = fortty_ft_render_glyph(font%ft_face, int(codepoint, c_int), &
176 bitmap_ptr, w, h, bx, by, adv)
177 if (err /= 0) then
178 bitmap_ptr = c_null_ptr
179 return
180 end if
181
182 glyph%codepoint = codepoint
183 glyph%width = w
184 glyph%height = h
185 glyph%bearing_x = bx
186 glyph%bearing_y = by
187 glyph%advance = adv
188 glyph%valid = .true.
189
190 end function font_render_glyph
191
192 ! Load a fallback font (for missing glyphs)
193 subroutine font_load_fallback(font, path)
194 type(font_t), intent(inout) :: font
195 character(len=*), intent(in) :: path
196 character(len=256) :: c_path
197 integer(c_int) :: err
198 integer(c_int) :: fb_cell_w, fb_cell_h, fb_asc, fb_desc
199
200 if (.not. font%loaded) return
201 if (len_trim(path) == 0) return
202
203 c_path = trim(path) // c_null_char
204 err = fortty_ft_load_font(font%ft_library, c_path, int(font%size_px, c_int), &
205 font%ft_face_fallback)
206 if (err /= 0) then
207 ! Silently fail - fallback is optional
208 font%ft_face_fallback = c_null_ptr
209 font%has_fallback = .false.
210 return
211 end if
212
213 font%has_fallback = .true.
214
215 ! Get fallback font metrics for baseline alignment
216 call fortty_ft_get_metrics(font%ft_face_fallback, fb_cell_w, fb_cell_h, fb_asc, fb_desc)
217 font%fallback_ascender = fb_asc
218 end subroutine font_load_fallback
219
220 ! Check if a glyph exists in the primary font
221 function font_has_glyph(font, codepoint) result(has)
222 type(font_t), intent(in) :: font
223 integer, intent(in) :: codepoint
224 logical :: has
225
226 has = .false.
227 if (.not. font%loaded) return
228
229 has = fortty_ft_has_glyph(font%ft_face, int(codepoint, c_int)) /= 0
230 end function font_has_glyph
231
232 ! Render glyph with fallback support
233 ! used_fallback is set to .true. if glyph came from fallback font
234 function font_render_glyph_with_fallback(font, codepoint, bitmap_ptr, used_fallback) result(glyph)
235 type(font_t), intent(in) :: font
236 integer, intent(in) :: codepoint
237 type(c_ptr), intent(out) :: bitmap_ptr
238 logical, intent(out) :: used_fallback
239 type(glyph_t) :: glyph
240 integer(c_int) :: err, w, h, bx, by, adv
241 type(c_ptr) :: face_to_use
242 integer(c_int) :: primary_has, fallback_has
243
244 call glyph_init(glyph)
245 used_fallback = .false.
246
247 if (.not. font%loaded) then
248 bitmap_ptr = c_null_ptr
249 return
250 end if
251
252 ! Check if primary font has this glyph
253 primary_has = fortty_ft_has_glyph(font%ft_face, int(codepoint, c_int))
254 if (primary_has /= 0) then
255 face_to_use = font%ft_face
256 else if (font%has_fallback .and. c_associated(font%ft_face_fallback)) then
257 ! Try fallback font
258 fallback_has = fortty_ft_has_glyph(font%ft_face_fallback, int(codepoint, c_int))
259 if (fallback_has /= 0) then
260 face_to_use = font%ft_face_fallback
261 used_fallback = .true.
262 else
263 ! Neither font has this glyph
264 bitmap_ptr = c_null_ptr
265 return
266 end if
267 else
268 ! No fallback available, try primary anyway (may show .notdef)
269 face_to_use = font%ft_face
270 end if
271
272 err = fortty_ft_render_glyph(face_to_use, int(codepoint, c_int), &
273 bitmap_ptr, w, h, bx, by, adv)
274 if (err /= 0) then
275 bitmap_ptr = c_null_ptr
276 return
277 end if
278
279 glyph%codepoint = codepoint
280 glyph%width = w
281 glyph%height = h
282 glyph%bearing_x = bx
283 glyph%bearing_y = by
284 glyph%advance = adv
285 glyph%valid = .true.
286
287 ! Normalize baseline: adjust bearing_y for ascender difference between fonts
288 if (used_fallback) then
289 glyph%bearing_y = glyph%bearing_y - (font%fallback_ascender - font%ascender)
290 end if
291
292 end function font_render_glyph_with_fallback
293
294 ! Find a font that supports a specific codepoint using fontconfig
295 ! Returns empty string if fontconfig not available or no font found
296 function font_find_for_codepoint(codepoint) result(path)
297 integer, intent(in) :: codepoint
298 character(len=256) :: path
299 character(len=256) :: c_path
300 integer(c_int) :: err
301 integer :: i
302
303 path = ''
304 c_path = ''
305 err = fortty_fc_find_font_for_char(int(codepoint, c_int), c_path, 256_c_int)
306 if (err == 0) then
307 ! Convert C string to Fortran string
308 do i = 1, 256
309 if (c_path(i:i) == c_null_char) exit
310 path(i:i) = c_path(i:i)
311 end do
312 end if
313 end function font_find_for_codepoint
314
315 ! Find a monospace font using fontconfig
316 ! Returns empty string if fontconfig not available or no font found
317 function font_find_monospace() result(path)
318 character(len=256) :: path
319 character(len=256) :: c_path
320 integer(c_int) :: err
321 integer :: i
322
323 path = ''
324 c_path = ''
325 err = fortty_fc_find_monospace_font(c_path, 256_c_int)
326 if (err == 0) then
327 ! Convert C string to Fortran string
328 do i = 1, 256
329 if (c_path(i:i) == c_null_char) exit
330 path(i:i) = c_path(i:i)
331 end do
332 end if
333 end function font_find_monospace
334
335 end module font_mod
336