summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Translate/utils/Font.php
blob: 37fa4ac77dadab675974df55777ee66a7732306f (plain)
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
<?php
/**
 * Contains class with wrapper around font-config.
 *
 * @author Niklas Laxström
 * @author Harry Burt
 * @license Unlicense
 * @file
 */

/**
 * Wrapper around font-config to get useful ttf font given a language code.
 * Uses wfShellExec, wfEscapeShellArg and wfDebugLog, wfGetCache and
 * wfMemckey from %MediaWiki.
 *
 * @ingroup Stats
 */
class FCFontFinder {
	/**
	 * Searches for suitable font in the system.
	 * @param string $code Language code.
	 * @return bool|string Full path to the font file, false on failure
	 */
	public static function findFile( $code ) {
		$data = self::callFontConfig( $code );
		if ( is_array( $data ) ) {
			return $data['file'];
		}

		return false;
	}

	/**
	 * Searches for suitable font family in the system.
	 * @param string $code Language code.
	 * @return bool|string Name of font family, false on failure
	 */
	public static function findFamily( $code ) {
		$data = self::callFontConfig( $code );
		if ( is_array( $data ) ) {
			return $data['family'];
		}

		return false;
	}

	protected static function callFontConfig( $code ) {
		if ( ini_get( 'open_basedir' ) ) {
			wfDebugLog( 'fcfont', 'Disabled because of open_basedir is active' );

			// Most likely we can't access any fonts we might find
			return false;
		}

		$cache = self::getCache();
		$cachekey = wfMemcKey( 'fcfont', $code );
		$timeout = 60 * 60 * 12;

		$cached = $cache->get( $cachekey );
		if ( is_array( $cached ) ) {
			return $cached;
		} elseif ( $cached === 'NEGATIVE' ) {
			return false;
		}

		$code = wfEscapeShellArg( ":lang=$code" );
		$ok = 0;
		$cmd = "fc-match $code";
		$suggestion = wfShellExec( $cmd, $ok );

		wfDebugLog( 'fcfont', "$cmd returned $ok" );

		if ( $ok !== 0 ) {
			wfDebugLog( 'fcfont', "fc-match error output: $suggestion" );
			$cache->set( $cachekey, 'NEGATIVE', $timeout );

			return false;
		}

		$pattern = '/^(.*?): "(.*)" "(.*)"$/';
		$matches = [];

		if ( !preg_match( $pattern, $suggestion, $matches ) ) {
			wfDebugLog( 'fcfont', "fc-match: return format not understood: $suggestion" );
			$cache->set( $cachekey, 'NEGATIVE', $timeout );

			return false;
		}

		list( , $file, $family, $type ) = $matches;
		wfDebugLog( 'fcfont', "fc-match: got $file: $family $type" );

		$file = wfEscapeShellArg( $file );
		$family = wfEscapeShellArg( $family );
		$type = wfEscapeShellArg( $type );
		$cmd = "fc-list $family $type $code file | grep $file";

		$candidates = trim( wfShellExec( $cmd, $ok ) );

		wfDebugLog( 'fcfont', "$cmd returned $ok" );

		if ( $ok !== 0 ) {
			wfDebugLog( 'fcfont', "fc-list error output: $candidates" );
			$cache->set( $cachekey, 'NEGATIVE', $timeout );

			return false;
		}

		# trim spaces
		$files = array_map( 'trim', explode( "\n", $candidates ) );
		$count = count( $files );
		if ( !$count ) {
			wfDebugLog( 'fcfont', "fc-list got zero canditates: $candidates" );
		}

		# remove the trailing ":"
		$chosen = substr( $files[0], 0, -1 );

		wfDebugLog( 'fcfont', "fc-list got $count candidates; using $chosen" );

		$data = [
			'family' => $family,
			'type' => $type,
			'file' => $chosen,
		];

		$cache->set( $cachekey, $data, $timeout );

		return $data;
	}

	/**
	 * @return BagOStuff
	 */
	protected static function getCache() {
		return wfGetCache( CACHE_ANYTHING );
	}
}