source: appstream-generator/src/asgen/handlers/fonthandler.d @ 4841

Last change on this file since 4841 was 4841, checked in by Juanma, 2 years ago

Initial release

File size: 8.9 KB
Line 
1/*
2 * Copyright (C) 2016 Matthias Klumpp <matthias@tenstral.net>
3 *
4 * Licensed under the GNU Lesser General Public License Version 3
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation, either version 3 of the license, or
9 * (at your option) any later version.
10 *
11 * This software is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this software.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20module asgen.handlers.fonthandler;
21
22import std.path : baseName, buildPath;
23import std.array : appender, replace, empty;
24import std.string : format, fromStringz, startsWith, endsWith, strip, toLower;
25import std.conv : to;
26import appstream.Component;
27import appstream.Icon;
28import appstream.Screenshot;
29static import appstream.Image;
30static import std.file;
31
32import asgen.utils;
33import asgen.logging;
34import asgen.result;
35import asgen.image : Canvas;
36import asgen.font : Font;
37import asgen.handlers.iconhandler : wantedIconSizes;
38
39
40private immutable fontScreenshotSizes = [ImageSize (1024, 78), ImageSize (640, 48)];
41
42void processFontData (GeneratorResult gres, string mediaExportDir)
43{
44    // create a map of all fonts we have in this package
45    Font[string] allFonts;
46    foreach (ref fname; gres.pkg.contents) {
47        if (!fname.startsWith ("/usr/share/fonts/"))
48            continue;
49        if (!fname.endsWith (".ttf", ".otf"))
50            continue;
51        // TODO: Can we support more font types?
52
53        const(ubyte)[] fdata;
54        try {
55            fdata = gres.pkg.getFileData (fname);
56        } catch (Exception e) {
57            gres.addHint (null, "pkg-extract-error", ["fname": fname.baseName, "pkg_fname": gres.pkg.filename.baseName, "error": e.msg]);
58            return;
59        }
60
61        immutable fontBaseName = fname.baseName;
62        logDebug ("Reading font %s", fontBaseName);
63
64        // the font class locks the global mutex internally when reading data with Fontconfig
65        Font font;
66        try {
67            font = new Font (fdata, fontBaseName);
68        } catch (Exception e) {
69            gres.addHint (null, "font-load-error", ["fname": fontBaseName, "pkg_fname": gres.pkg.filename.baseName, "error": e.msg]);
70            return;
71        }
72        allFonts[font.fullName.toLower] = font;
73    }
74
75    foreach (ref cpt; gres.getComponents ()) {
76        if (cpt.getKind () != ComponentKind.FONT)
77            continue;
78
79        processFontDataForComponent (gres, cpt, allFonts, mediaExportDir);
80    }
81}
82
83void processFontDataForComponent (GeneratorResult gres, Component cpt, ref Font[string] allFonts, string mediaExportDir)
84{
85    immutable gcid = gres.gcidForComponent (cpt);
86    if (gcid is null) {
87        gres.addHint (cpt, "internal-error", "No global ID could be found for the component.");
88        return;
89    }
90
91    auto fontHints = appender!(string[]);
92    auto provided = cpt.getProvidedForKind (ProvidedKind.FONT);
93    if (provided !is null) {
94        auto fontHintsArr = provided.getItems ();
95
96        for (uint i = 0; i < fontHintsArr.len; i++) {
97            auto fontFullName = (cast(char*) fontHintsArr.index (i)).fromStringz;
98            fontHints ~= to!string (fontFullName).toLower;
99        }
100    }
101
102    // data export paths
103    immutable cptIconsPath = buildPath (mediaExportDir, gcid, "icons");
104    immutable cptScreenshotsPath = buildPath (mediaExportDir, gcid, "screenshots");
105
106    // if we have no fonts hints, we simply process all the fonts
107    // we found n this package.
108    auto selectedFonts = appender!(Font[]);
109    if (fontHints.data.length == 0) {
110        foreach (ref font; allFonts.byValue)
111            selectedFonts ~= font;
112    } else {
113        // find fonts based on the hints we have
114        // the hint as well as the dictionary keys are all lowercased, so we
115        // can do case-insensitive matching here.
116        foreach (ref fontHint; fontHints.data) {
117            auto fontP = fontHint in allFonts;
118            if (fontP is null)
119                continue;
120            selectedFonts ~= *fontP;
121        }
122    }
123
124    // we have nothing to do if we did not select any font
125    // (this is a bug, since we filtered for font metainfo previously)
126    if (selectedFonts.data.length == 0) {
127        gres.addHint (cpt, "font-metainfo-but-no-font");
128        return;
129    }
130
131    logDebug ("Rendering font data for %s", gcid);
132
133    // process font files
134    auto hasIcon = false;
135    foreach (ref font; selectedFonts.data) {
136        logDebug ("Processing font '%s'", font.id);
137
138        // add language information
139        foreach (ref lang; font.languages) {
140            cpt.addLanguage (lang, 80);
141        }
142
143        // render an icon for our font
144        if (!hasIcon)
145            hasIcon = renderFontIcon (gres,
146                                      font,
147                                      cptIconsPath,
148                                      cpt);
149    }
150
151    // render all sample screenshots for all font styles we have
152    renderFontScreenshots (gres,
153                           selectedFonts.data,
154                           cptScreenshotsPath,
155                           cpt);
156}
157
158/**
159 * Render an icon for this font package using one of its fonts.
160 * (Since we have no better way to do this, we just pick the first font
161 * at time)
162 **/
163private bool renderFontIcon (GeneratorResult gres, Font font, immutable string cptIconsPath, Component cpt)
164{
165    foreach (ref size; wantedIconSizes) {
166        immutable path = buildPath (cptIconsPath, size.toString);
167        std.file.mkdirRecurse (path);
168
169        // check if we have a custom icon text value (useful for symbolic fonts)
170        immutable customIconText = cpt.getCustomValue ("FontIconText");
171        if (!customIconText.empty)
172            font.sampleIconText = customIconText; // Font will ensure that the value does not exceed 3 chars
173
174        immutable fid = font.id;
175        immutable iconName = format ("%s_%s.png", gres.pkgname,  fid);
176        immutable iconStoreLocation = buildPath (path, iconName);
177
178        if (!std.file.exists (iconStoreLocation)) {
179            // we didn't create an icon yet - render it
180            auto cv = new Canvas (size.width, size.height);
181            cv.drawTextLine (font, font.sampleIconText);
182            cv.savePng (iconStoreLocation);
183        }
184
185        auto icon = new Icon ();
186        icon.setKind (IconKind.CACHED);
187        icon.setWidth (size.width);
188        icon.setHeight (size.height);
189        icon.setName (iconName);
190        cpt.addIcon (icon);
191    }
192
193    return true;
194}
195
196/**
197 * Render a "screenshot" sample for this font.
198 **/
199private bool renderFontScreenshots (GeneratorResult gres, Font[] fonts, immutable string cptScreenshotsPath, Component cpt)
200{
201    std.file.mkdirRecurse (cptScreenshotsPath);
202
203    auto first = true;
204    foreach (ref font; fonts) {
205        immutable fid = font.id;
206        if (fid is null) {
207            logWarning ("%s: Ignored font screenshot rendering due to missing ID:", cpt.getId ());
208            continue;
209        }
210
211        auto scr = new Screenshot ();
212        if (first)
213            scr.setKind (ScreenshotKind.DEFAULT);
214        else
215            scr.setKind (ScreenshotKind.EXTRA);
216        scr.setCaption ("%s %s".format (font.family, font.style), "C");
217
218        if (first)
219            first = false;
220
221        // check if we have a custom sample text value (useful for symbolic fonts)
222        // we set this value for every fonr in the font-bundle, there is no way for this
223        // hack to select which font face should have the sample text.
224        // Since this hack only affects very few exotic fonts and should generally not
225        // be used, this should not be an issue.
226        immutable customSampleText = cpt.getCustomValue ("FontSampleText");
227        if (!customSampleText.empty)
228            font.sampleIconText = customSampleText;
229
230        auto cptScreenshotsUrl = buildPath (gres.gcidForComponent (cpt), "screenshots");
231        foreach (ref size; fontScreenshotSizes) {
232            immutable imgName = "image-%s_%s.png".format (fid, size.toString);
233            immutable imgFileName = buildPath (cptScreenshotsPath, imgName);
234            immutable imgUrl = buildPath (cptScreenshotsUrl, imgName);
235
236
237            if (!std.file.exists (imgFileName)) {
238                // we didn't create s screenshot yet - render it
239                auto cv = new Canvas (size.width, size.height);
240                cv.drawTextLine (font, font.sampleText);
241                cv.savePng (imgFileName);
242            }
243
244            auto img = new appstream.Image.Image ();
245            img.setKind (ImageKind.THUMBNAIL);
246            img.setWidth (size.width);
247            img.setHeight (size.height);
248            img.setUrl (imgUrl);
249
250            scr.addImage (img);
251        }
252
253        cpt.addScreenshot (scr);
254    }
255
256    return true;
257}
Note: See TracBrowser for help on using the repository browser.