1 | /* Pango |
2 | * pangoft2.c: Routines for handling FreeType2 fonts |
3 | * |
4 | * Copyright (C) 1999 Red Hat Software |
5 | * Copyright (C) 2000 Tor Lillqvist |
6 | * |
7 | * This library is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Library General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2 of the License, or (at your option) any later version. |
11 | * |
12 | * This library is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Library General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Library General Public |
18 | * License along with this library; if not, write to the |
19 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
20 | * Boston, MA 02111-1307, USA. |
21 | */ |
22 | |
23 | #include "config.h" |
24 | |
25 | #include <string.h> |
26 | #include <stdlib.h> |
27 | #include <math.h> |
28 | #include <glib.h> |
29 | #include <glib/gprintf.h> |
30 | |
31 | #include "pangoft2.h" |
32 | #include "pangoft2-private.h" |
33 | #include "pangofc-fontmap-private.h" |
34 | #include "pangofc-private.h" |
35 | |
36 | /* for compatibility with older freetype versions */ |
37 | #ifndef FT_LOAD_TARGET_MONO |
38 | #define FT_LOAD_TARGET_MONO FT_LOAD_MONOCHROME |
39 | #endif |
40 | |
41 | #define PANGO_FT2_FONT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PANGO_TYPE_FT2_FONT, PangoFT2FontClass)) |
42 | #define PANGO_FT2_IS_FONT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PANGO_TYPE_FT2_FONT)) |
43 | #define PANGO_FT2_FONT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PANGO_TYPE_FT2_FONT, PangoFT2FontClass)) |
44 | |
45 | typedef struct _PangoFT2FontClass PangoFT2FontClass; |
46 | |
47 | struct _PangoFT2FontClass |
48 | { |
49 | PangoFcFontClass parent_class; |
50 | }; |
51 | |
52 | static void pango_ft2_font_finalize (GObject *object); |
53 | |
54 | static void pango_ft2_font_get_glyph_extents (PangoFont *font, |
55 | PangoGlyph glyph, |
56 | PangoRectangle *ink_rect, |
57 | PangoRectangle *logical_rect); |
58 | |
59 | static FT_Face pango_ft2_font_real_lock_face (PangoFcFont *font); |
60 | static void pango_ft2_font_real_unlock_face (PangoFcFont *font); |
61 | |
62 | |
63 | PangoFT2Font * |
64 | _pango_ft2_font_new (PangoFT2FontMap *ft2fontmap, |
65 | FcPattern *pattern) |
66 | { |
67 | PangoFontMap *fontmap = PANGO_FONT_MAP (ft2fontmap); |
68 | PangoFT2Font *ft2font; |
69 | double d; |
70 | |
71 | g_return_val_if_fail (fontmap != NULL, NULL); |
72 | g_return_val_if_fail (pattern != NULL, NULL); |
73 | |
74 | ft2font = (PangoFT2Font *)g_object_new (PANGO_TYPE_FT2_FONT, |
75 | first_property_name: "pattern" , pattern, |
76 | "fontmap" , fontmap, |
77 | NULL); |
78 | |
79 | if (FcPatternGetDouble (p: pattern, FC_PIXEL_SIZE, n: 0, d: &d) == FcResultMatch) |
80 | ft2font->size = d*PANGO_SCALE; |
81 | |
82 | return ft2font; |
83 | } |
84 | |
85 | static void |
86 | load_fallback_face (PangoFT2Font *ft2font, |
87 | const char *original_file) |
88 | { |
89 | PangoFcFont *fcfont = PANGO_FC_FONT (ft2font); |
90 | FcPattern *sans; |
91 | FcPattern *matched; |
92 | FcResult result; |
93 | FT_Error error; |
94 | FcChar8 *filename2 = NULL; |
95 | gchar *name; |
96 | int id; |
97 | |
98 | sans = FcPatternBuild (NULL, |
99 | FC_FAMILY, FcTypeString, "sans" , |
100 | FC_PIXEL_SIZE, FcTypeDouble, (double)ft2font->size / PANGO_SCALE, |
101 | NULL); |
102 | |
103 | _pango_ft2_font_map_default_substitute (fcfontmap: (PangoFcFontMap *)fcfont->fontmap, pattern: sans); |
104 | |
105 | matched = FcFontMatch (config: pango_fc_font_map_get_config (fcfontmap: (PangoFcFontMap *)fcfont->fontmap), p: sans, result: &result); |
106 | |
107 | if (FcPatternGetString (p: matched, FC_FILE, n: 0, s: &filename2) != FcResultMatch) |
108 | goto bail1; |
109 | |
110 | if (FcPatternGetInteger (p: matched, FC_INDEX, n: 0, i: &id) != FcResultMatch) |
111 | goto bail1; |
112 | |
113 | error = FT_New_Face (library: _pango_ft2_font_map_get_library (fontmap: fcfont->fontmap), |
114 | filepathname: (char *) filename2, face_index: id, aface: &ft2font->face); |
115 | |
116 | |
117 | if (error) |
118 | { |
119 | bail1: |
120 | name = pango_font_description_to_string (desc: fcfont->description); |
121 | g_error ("Unable to open font file %s for font %s, exiting\n" , filename2, name); |
122 | } |
123 | else |
124 | { |
125 | name = pango_font_description_to_string (desc: fcfont->description); |
126 | g_warning ("Unable to open font file %s for font %s, falling back to %s\n" , original_file, name, filename2); |
127 | g_free (mem: name); |
128 | } |
129 | |
130 | FcPatternDestroy (p: sans); |
131 | FcPatternDestroy (p: matched); |
132 | } |
133 | |
134 | static void |
135 | set_transform (PangoFT2Font *ft2font) |
136 | { |
137 | PangoFcFont *fcfont = (PangoFcFont *)ft2font; |
138 | FcMatrix *fc_matrix; |
139 | |
140 | if (FcPatternGetMatrix (p: fcfont->font_pattern, FC_MATRIX, n: 0, s: &fc_matrix) == FcResultMatch) |
141 | { |
142 | FT_Matrix ft_matrix; |
143 | |
144 | ft_matrix.xx = 0x10000L * fc_matrix->xx; |
145 | ft_matrix.yy = 0x10000L * fc_matrix->yy; |
146 | ft_matrix.xy = 0x10000L * fc_matrix->xy; |
147 | ft_matrix.yx = 0x10000L * fc_matrix->yx; |
148 | |
149 | FT_Set_Transform (face: ft2font->face, matrix: &ft_matrix, NULL); |
150 | } |
151 | } |
152 | |
153 | /** |
154 | * pango_ft2_font_get_face: (skip) |
155 | * @font: a `PangoFont` |
156 | * |
157 | * Returns the native FreeType2 `FT_Face` structure |
158 | * used for this `PangoFont`. |
159 | * |
160 | * This may be useful if you want to use FreeType2 |
161 | * functions directly. |
162 | * |
163 | * Use [method@PangoFc.Font.lock_face] instead; when you are |
164 | * done with a face from [method@PangoFc.Font.lock_face], you |
165 | * must call [method@PangoFc.Font.unlock_face]. |
166 | * |
167 | * Return value: (nullable): a pointer to a `FT_Face` structure, |
168 | * with the size set correctly |
169 | */ |
170 | FT_Face |
171 | pango_ft2_font_get_face (PangoFont *font) |
172 | { |
173 | PangoFT2Font *ft2font = (PangoFT2Font *)font; |
174 | PangoFcFont *fcfont = (PangoFcFont *)font; |
175 | FT_Error error; |
176 | FcPattern *pattern; |
177 | FcChar8 *filename; |
178 | FcBool antialias, hinting, autohint; |
179 | int hintstyle; |
180 | int id; |
181 | |
182 | if (G_UNLIKELY (!font)) |
183 | return NULL; |
184 | |
185 | pattern = fcfont->font_pattern; |
186 | |
187 | if (!ft2font->face) |
188 | { |
189 | ft2font->load_flags = 0; |
190 | |
191 | /* disable antialiasing if requested */ |
192 | if (FcPatternGetBool (p: pattern, |
193 | FC_ANTIALIAS, n: 0, b: &antialias) != FcResultMatch) |
194 | antialias = FcTrue; |
195 | |
196 | if (antialias) |
197 | ft2font->load_flags |= FT_LOAD_NO_BITMAP; |
198 | else |
199 | ft2font->load_flags |= FT_LOAD_TARGET_MONO; |
200 | |
201 | /* disable hinting if requested */ |
202 | if (FcPatternGetBool (p: pattern, |
203 | FC_HINTING, n: 0, b: &hinting) != FcResultMatch) |
204 | hinting = FcTrue; |
205 | |
206 | #ifdef FC_HINT_STYLE |
207 | if (FcPatternGetInteger (p: pattern, FC_HINT_STYLE, n: 0, i: &hintstyle) != FcResultMatch) |
208 | hintstyle = FC_HINT_FULL; |
209 | |
210 | if (!hinting || hintstyle == FC_HINT_NONE) |
211 | ft2font->load_flags |= FT_LOAD_NO_HINTING; |
212 | |
213 | switch (hintstyle) { |
214 | case FC_HINT_SLIGHT: |
215 | case FC_HINT_MEDIUM: |
216 | ft2font->load_flags |= FT_LOAD_TARGET_LIGHT; |
217 | break; |
218 | default: |
219 | ft2font->load_flags |= FT_LOAD_TARGET_NORMAL; |
220 | break; |
221 | } |
222 | #else |
223 | if (!hinting) |
224 | ft2font->load_flags |= FT_LOAD_NO_HINTING; |
225 | #endif |
226 | |
227 | /* force autohinting if requested */ |
228 | if (FcPatternGetBool (p: pattern, |
229 | FC_AUTOHINT, n: 0, b: &autohint) != FcResultMatch) |
230 | autohint = FcFalse; |
231 | |
232 | if (autohint) |
233 | ft2font->load_flags |= FT_LOAD_FORCE_AUTOHINT; |
234 | |
235 | if (FcPatternGetString (p: pattern, FC_FILE, n: 0, s: &filename) != FcResultMatch) |
236 | goto bail0; |
237 | |
238 | if (FcPatternGetInteger (p: pattern, FC_INDEX, n: 0, i: &id) != FcResultMatch) |
239 | goto bail0; |
240 | |
241 | error = FT_New_Face (library: _pango_ft2_font_map_get_library (fontmap: fcfont->fontmap), |
242 | filepathname: (char *) filename, face_index: id, aface: &ft2font->face); |
243 | if (error != FT_Err_Ok) |
244 | { |
245 | bail0: |
246 | load_fallback_face (ft2font, original_file: (char *) filename); |
247 | } |
248 | |
249 | g_assert (ft2font->face); |
250 | |
251 | set_transform (ft2font); |
252 | |
253 | error = FT_Set_Char_Size (face: ft2font->face, |
254 | PANGO_PIXELS_26_6 (ft2font->size), |
255 | PANGO_PIXELS_26_6 (ft2font->size), |
256 | horz_resolution: 0, vert_resolution: 0); |
257 | if (error) |
258 | g_warning ("Error in FT_Set_Char_Size: %d" , error); |
259 | } |
260 | |
261 | return ft2font->face; |
262 | } |
263 | |
264 | G_DEFINE_TYPE (PangoFT2Font, pango_ft2_font, PANGO_TYPE_FC_FONT) |
265 | |
266 | static void |
267 | pango_ft2_font_init (PangoFT2Font *ft2font) |
268 | { |
269 | ft2font->face = NULL; |
270 | |
271 | ft2font->size = 0; |
272 | |
273 | ft2font->glyph_info = g_hash_table_new (NULL, NULL); |
274 | } |
275 | |
276 | static void |
277 | pango_ft2_font_class_init (PangoFT2FontClass *class) |
278 | { |
279 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
280 | PangoFontClass *font_class = PANGO_FONT_CLASS (class); |
281 | PangoFcFontClass *fc_font_class = PANGO_FC_FONT_CLASS (class); |
282 | |
283 | object_class->finalize = pango_ft2_font_finalize; |
284 | |
285 | font_class->get_glyph_extents = pango_ft2_font_get_glyph_extents; |
286 | |
287 | fc_font_class->lock_face = pango_ft2_font_real_lock_face; |
288 | fc_font_class->unlock_face = pango_ft2_font_real_unlock_face; |
289 | } |
290 | |
291 | static PangoFT2GlyphInfo * |
292 | pango_ft2_font_get_glyph_info (PangoFont *font, |
293 | PangoGlyph glyph, |
294 | gboolean create) |
295 | { |
296 | PangoFT2Font *ft2font = (PangoFT2Font *)font; |
297 | PangoFcFont *fcfont = (PangoFcFont *)font; |
298 | PangoFT2GlyphInfo *info; |
299 | |
300 | info = g_hash_table_lookup (hash_table: ft2font->glyph_info, GUINT_TO_POINTER (glyph)); |
301 | |
302 | if ((info == NULL) && create) |
303 | { |
304 | info = g_slice_new0 (PangoFT2GlyphInfo); |
305 | |
306 | pango_fc_font_get_raw_extents (font: fcfont, |
307 | glyph, |
308 | ink_rect: &info->ink_rect, |
309 | logical_rect: &info->logical_rect); |
310 | |
311 | g_hash_table_insert (hash_table: ft2font->glyph_info, GUINT_TO_POINTER(glyph), value: info); |
312 | } |
313 | |
314 | return info; |
315 | } |
316 | |
317 | static void |
318 | pango_ft2_font_get_glyph_extents (PangoFont *font, |
319 | PangoGlyph glyph, |
320 | PangoRectangle *ink_rect, |
321 | PangoRectangle *logical_rect) |
322 | { |
323 | PangoFT2GlyphInfo *info; |
324 | gboolean empty = FALSE; |
325 | |
326 | if (glyph == PANGO_GLYPH_EMPTY) |
327 | { |
328 | glyph = pango_fc_font_get_glyph (font: (PangoFcFont *) font, wc: ' '); |
329 | empty = TRUE; |
330 | } |
331 | |
332 | if (glyph & PANGO_GLYPH_UNKNOWN_FLAG) |
333 | { |
334 | PangoFontMetrics *metrics = pango_font_get_metrics (font, NULL); |
335 | |
336 | if (metrics) |
337 | { |
338 | if (ink_rect) |
339 | { |
340 | ink_rect->x = PANGO_SCALE; |
341 | ink_rect->width = metrics->approximate_char_width - 2 * PANGO_SCALE; |
342 | ink_rect->y = - (metrics->ascent - PANGO_SCALE); |
343 | ink_rect->height = metrics->ascent + metrics->descent - 2 * PANGO_SCALE; |
344 | } |
345 | if (logical_rect) |
346 | { |
347 | logical_rect->x = 0; |
348 | logical_rect->width = metrics->approximate_char_width; |
349 | logical_rect->y = -metrics->ascent; |
350 | logical_rect->height = metrics->ascent + metrics->descent; |
351 | } |
352 | |
353 | pango_font_metrics_unref (metrics); |
354 | } |
355 | else |
356 | { |
357 | if (ink_rect) |
358 | ink_rect->x = ink_rect->y = ink_rect->height = ink_rect->width = 0; |
359 | if (logical_rect) |
360 | logical_rect->x = logical_rect->y = logical_rect->height = logical_rect->width = 0; |
361 | } |
362 | return; |
363 | } |
364 | |
365 | info = pango_ft2_font_get_glyph_info (font, glyph, TRUE); |
366 | |
367 | if (ink_rect) |
368 | *ink_rect = info->ink_rect; |
369 | if (logical_rect) |
370 | *logical_rect = info->logical_rect; |
371 | |
372 | if (empty) |
373 | { |
374 | if (ink_rect) |
375 | ink_rect->x = ink_rect->y = ink_rect->height = ink_rect->width = 0; |
376 | if (logical_rect) |
377 | logical_rect->x = logical_rect->width = 0; |
378 | return; |
379 | } |
380 | } |
381 | |
382 | /** |
383 | * pango_ft2_font_get_kerning: |
384 | * @font: a `PangoFont` |
385 | * @left: the left `PangoGlyph` |
386 | * @right: the right `PangoGlyph` |
387 | * |
388 | * Retrieves kerning information for a combination of two glyphs. |
389 | * |
390 | * Use pango_fc_font_kern_glyphs() instead. |
391 | * |
392 | * Return value: The amount of kerning (in Pango units) to |
393 | * apply for the given combination of glyphs. |
394 | */ |
395 | int |
396 | pango_ft2_font_get_kerning (PangoFont *font, |
397 | PangoGlyph left, |
398 | PangoGlyph right) |
399 | { |
400 | PangoFcFont *fc_font = PANGO_FC_FONT (font); |
401 | |
402 | FT_Face face; |
403 | FT_Error error; |
404 | FT_Vector kerning; |
405 | |
406 | face = pango_fc_font_lock_face (font: fc_font); |
407 | if (!face) |
408 | return 0; |
409 | |
410 | if (!FT_HAS_KERNING (face)) |
411 | { |
412 | pango_fc_font_unlock_face (font: fc_font); |
413 | return 0; |
414 | } |
415 | |
416 | error = FT_Get_Kerning (face, left_glyph: left, right_glyph: right, ft_kerning_default, akerning: &kerning); |
417 | if (error != FT_Err_Ok) |
418 | { |
419 | pango_fc_font_unlock_face (font: fc_font); |
420 | return 0; |
421 | } |
422 | |
423 | pango_fc_font_unlock_face (font: fc_font); |
424 | return PANGO_UNITS_26_6 (kerning.x); |
425 | } |
426 | |
427 | static FT_Face |
428 | pango_ft2_font_real_lock_face (PangoFcFont *font) |
429 | { |
430 | return pango_ft2_font_get_face (font: (PangoFont *)font); |
431 | } |
432 | |
433 | static void |
434 | pango_ft2_font_real_unlock_face (PangoFcFont *font G_GNUC_UNUSED) |
435 | { |
436 | } |
437 | |
438 | static gboolean |
439 | pango_ft2_free_glyph_info_callback (gpointer key G_GNUC_UNUSED, |
440 | gpointer value, |
441 | gpointer data) |
442 | { |
443 | PangoFT2Font *font = PANGO_FT2_FONT (data); |
444 | PangoFT2GlyphInfo *info = value; |
445 | |
446 | if (font->glyph_cache_destroy && info->cached_glyph) |
447 | (*font->glyph_cache_destroy) (info->cached_glyph); |
448 | |
449 | g_slice_free (PangoFT2GlyphInfo, info); |
450 | return TRUE; |
451 | } |
452 | |
453 | static void |
454 | pango_ft2_font_finalize (GObject *object) |
455 | { |
456 | PangoFT2Font *ft2font = (PangoFT2Font *)object; |
457 | |
458 | if (ft2font->face) |
459 | { |
460 | FT_Done_Face (face: ft2font->face); |
461 | ft2font->face = NULL; |
462 | } |
463 | |
464 | g_hash_table_foreach_remove (hash_table: ft2font->glyph_info, |
465 | func: pango_ft2_free_glyph_info_callback, user_data: object); |
466 | g_hash_table_destroy (hash_table: ft2font->glyph_info); |
467 | |
468 | G_OBJECT_CLASS (pango_ft2_font_parent_class)->finalize (object); |
469 | } |
470 | |
471 | /** |
472 | * pango_ft2_font_get_coverage: |
473 | * @font: a Pango FT2 font |
474 | * @language: a language tag. |
475 | * |
476 | * Gets the `PangoCoverage` for a `PangoFT2Font`. |
477 | * |
478 | * Use [method@Pango.Font.get_coverage] instead. |
479 | * |
480 | * Return value: (transfer full): a `PangoCoverage` |
481 | */ |
482 | PangoCoverage * |
483 | pango_ft2_font_get_coverage (PangoFont *font, |
484 | PangoLanguage *language) |
485 | { |
486 | return pango_font_get_coverage (font, language); |
487 | } |
488 | |
489 | /* Utility functions */ |
490 | |
491 | /** |
492 | * pango_ft2_get_unknown_glyph: |
493 | * @font: a `PangoFont` |
494 | * |
495 | * Return the index of a glyph suitable for drawing unknown |
496 | * characters with @font, or %PANGO_GLYPH_EMPTY if no suitable |
497 | * glyph found. |
498 | * |
499 | * If you want to draw an unknown-box for a character that |
500 | * is not covered by the font, use PANGO_GET_UNKNOWN_GLYPH() |
501 | * instead. |
502 | * |
503 | * Return value: a glyph index into @font, or %PANGO_GLYPH_EMPTY |
504 | */ |
505 | PangoGlyph |
506 | pango_ft2_get_unknown_glyph (PangoFont *font) |
507 | { |
508 | FT_Face face = pango_ft2_font_get_face (font); |
509 | if (face && FT_IS_SFNT (face)) |
510 | /* TrueType fonts have an 'unknown glyph' box on glyph index 0 */ |
511 | return 0; |
512 | else |
513 | return PANGO_GLYPH_EMPTY; |
514 | } |
515 | |
516 | void * |
517 | _pango_ft2_font_get_cache_glyph_data (PangoFont *font, |
518 | int glyph_index) |
519 | { |
520 | PangoFT2GlyphInfo *info; |
521 | |
522 | if (!PANGO_FT2_IS_FONT (font)) |
523 | return NULL; |
524 | |
525 | info = pango_ft2_font_get_glyph_info (font, glyph: glyph_index, FALSE); |
526 | |
527 | if (info == NULL) |
528 | return NULL; |
529 | |
530 | return info->cached_glyph; |
531 | } |
532 | |
533 | void |
534 | _pango_ft2_font_set_cache_glyph_data (PangoFont *font, |
535 | int glyph_index, |
536 | void *cached_glyph) |
537 | { |
538 | PangoFT2GlyphInfo *info; |
539 | |
540 | if (!PANGO_FT2_IS_FONT (font)) |
541 | return; |
542 | |
543 | info = pango_ft2_font_get_glyph_info (font, glyph: glyph_index, TRUE); |
544 | |
545 | info->cached_glyph = cached_glyph; |
546 | |
547 | /* TODO: Implement limiting of the number of cached glyphs */ |
548 | } |
549 | |
550 | void |
551 | _pango_ft2_font_set_glyph_cache_destroy (PangoFont *font, |
552 | GDestroyNotify destroy_notify) |
553 | { |
554 | if (!PANGO_FT2_IS_FONT (font)) |
555 | return; |
556 | |
557 | PANGO_FT2_FONT (font)->glyph_cache_destroy = destroy_notify; |
558 | } |
559 | |