1 /** @file compositebitmapfont.cpp Composite bitmap font.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2006-2013 Daniel Swanson <danij@dengine.net>
5  *
6  * @par License
7  * GPL: http://www.gnu.org/licenses/gpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details. You should have received a copy of the GNU
16  * General Public License along with this program; if not, write to the Free
17  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18  * 02110-1301 USA</small>
19  */
20 
21 #include "clientapp.h"
22 #include "resource/compositebitmapfont.h"
23 
24 #include <doomsday/res/Textures>
25 
26 #include "api_resource.h" // R_GetPatchInfo
27 #include "dd_main.h" // App_Resources(), isDedicated
28 #include "sys_system.h" // novideo
29 #include "gl/gl_texmanager.h" // GL_TextureVariantSpec
30 
31 using namespace de;
32 
DENG2_PIMPL(CompositeBitmapFont)33 DENG2_PIMPL(CompositeBitmapFont)
34 {
35     ded_compositefont_t *def; /// Definition on which "this" font is derived (if any).
36     bool needGLInit;
37 
38     /// Font metrics.
39     int leading;
40     int ascent;
41     int descent;
42 
43     Glyph glyphs[MAX_CHARS];
44     Glyph missingGlyph;
45 
46     Impl(Public *i)
47         : Base(i)
48         , def(0)
49         , needGLInit(true)
50         , leading(0)
51         , ascent(0)
52         , descent(0)
53     {
54         zap(glyphs);
55         zap(missingGlyph);
56         self()._flags |= AbstractFont::Colorize;
57     }
58 
59     ~Impl()
60     {
61         self().glDeinit();
62     }
63 
64     Glyph &glyph(uchar ch)
65     {
66         //if(ch >= MAX_CHARS) return missingGlyph;
67         if(!glyphs[ch].haveSourceImage) return missingGlyph;
68         return glyphs[ch];
69     }
70 };
71 
CompositeBitmapFont(FontManifest & manifest)72 CompositeBitmapFont::CompositeBitmapFont(FontManifest &manifest)
73     : AbstractFont(manifest), d(new Impl(this))
74 {}
75 
ascent() const76 int CompositeBitmapFont::ascent() const
77 {
78     glInit();
79     return d->ascent;
80 }
81 
descent() const82 int CompositeBitmapFont::descent() const
83 {
84     glInit();
85     return d->descent;
86 }
87 
lineSpacing() const88 int CompositeBitmapFont::lineSpacing() const
89 {
90     glInit();
91     return d->leading;
92 }
93 
glyphPosCoords(uchar ch) const94 Rectanglei const &CompositeBitmapFont::glyphPosCoords(uchar ch) const
95 {
96     glInit();
97     return d->glyph(ch).geometry;
98 }
99 
glyphTexCoords(uchar) const100 Rectanglei const &CompositeBitmapFont::glyphTexCoords(uchar /*ch*/) const
101 {
102     static Rectanglei coords(Vector2i(0, 0), Vector2i(1, 1));
103     glInit();
104     return coords;
105 }
106 
glyphTextureBorder(uchar ch) const107 uint CompositeBitmapFont::glyphTextureBorder(uchar ch) const
108 {
109     glInit();
110     return d->glyph(ch).border;
111 }
112 
glyphTexture(uchar ch) const113 TextureVariant *CompositeBitmapFont::glyphTexture(uchar ch) const
114 {
115     glInit();
116     return d->glyph(ch).tex;
117 }
118 
glyphPatch(uchar ch) const119 patchid_t CompositeBitmapFont::glyphPatch(uchar ch) const
120 {
121     glInit();
122     return d->glyph(ch).patch;
123 }
124 
glyphSetPatch(uchar ch,String encodedPatchName)125 void CompositeBitmapFont::glyphSetPatch(uchar ch, String encodedPatchName)
126 {
127     //if(ch >= MAX_CHARS) return;
128     d->glyphs[ch].patch = res::Textures::get().declarePatch(encodedPatchName);
129 
130     // We'll need to rebuild the prepared GL resources.
131     d->needGLInit = true;
132 }
133 
134 /// @todo fixme: Do not assume the texture-usage context is @c TC_UI.
glyphTextureSpec()135 static TextureVariantSpec const &glyphTextureSpec()
136 {
137     return ClientApp::resources().textureSpec(TC_UI,
138         TSF_MONOCHROME | TSF_UPSCALE_AND_SHARPEN, 0, 0, 0, GL_CLAMP_TO_EDGE,
139         GL_CLAMP_TO_EDGE, 0, -3, 0, false, false, false, false);
140 }
141 
glInit() const142 void CompositeBitmapFont::glInit() const
143 {
144     if(!d->needGLInit) return;
145     if(novideo || BusyMode_Active()) return;
146 
147     LOG_AS("CompositeBitmapFont");
148 
149     glDeinit();
150 
151     int foundGlyphs = 0;
152     Vector2ui avgSize;
153     for(int i = 0; i < MAX_CHARS; ++i)
154     {
155         Glyph *ch = &d->glyphs[i];
156         patchid_t patch = ch->patch;
157 
158         ch->haveSourceImage = patch != 0;
159         if(!ch->haveSourceImage) continue;
160 
161         try
162         {
163             res::Texture &tex = res::Textures::get().textureScheme("Patches").findByUniqueId(patch).texture();
164 
165             ch->tex      = static_cast<ClientTexture &>(tex).prepareVariant(glyphTextureSpec());
166             ch->geometry = Rectanglei::fromSize(tex.origin(), tex.dimensions().toVector2ui());
167 
168             ch->border   = 0;
169             if(ch->tex && ch->tex->source() == res::Original)
170             {
171                 // Upscale & Sharpen will have been applied.
172                 ch->border = 1;
173             }
174 
175             avgSize += ch->geometry.size();
176             ++foundGlyphs;
177         }
178         catch(res::TextureManifest::MissingTextureError const &er)
179         {
180             // Log but otherwise ignore this error.
181             LOG_RES_WARNING(er.asText() + ", ignoring.");
182         }
183         catch(res::TextureScheme::NotFoundError const &er)
184         {
185             // Log but otherwise ignore this error.
186             LOG_RES_WARNING(er.asText() + ", ignoring.");
187         }
188     }
189 
190     d->missingGlyph.geometry.setSize(avgSize / (foundGlyphs? foundGlyphs : 1));
191 
192     // We have prepared all patches.
193     d->needGLInit = false;
194 }
195 
glDeinit() const196 void CompositeBitmapFont::glDeinit() const
197 {
198     if(novideo) return;
199 
200     d->needGLInit = true;
201     if(BusyMode_Active()) return;
202 
203     for(int i = 0; i < 256; ++i)
204     {
205         Glyph *ch = &d->glyphs[i];
206         if(!ch->tex) continue;
207         ch->tex->release();
208         ch->tex = 0;
209     }
210 }
211 
fromDef(FontManifest & manifest,ded_compositefont_t const & def)212 CompositeBitmapFont *CompositeBitmapFont::fromDef(FontManifest &manifest,
213     ded_compositefont_t const &def) // static
214 {
215     LOG_AS("CompositeBitmapFont::fromDef");
216 
217     CompositeBitmapFont *font = new CompositeBitmapFont(manifest);
218     font->setDefinition(const_cast<ded_compositefont_t *>(&def));
219 
220     for(int i = 0; i < def.charMap.size(); ++i)
221     {
222         if(!def.charMap[i].path) continue;
223         try
224         {
225             String glyphPatchPath = def.charMap[i].path->resolved();
226             font->glyphSetPatch(def.charMap[i].ch, glyphPatchPath);
227         }
228         catch(de::Uri::ResolveError const &er)
229         {
230             LOG_RES_WARNING(er.asText());
231         }
232     }
233 
234     // Lets try to prepare it right away.
235     font->glInit();
236     return font;
237 }
238 
definition() const239 ded_compositefont_t *CompositeBitmapFont::definition() const
240 {
241     return d->def;
242 }
243 
setDefinition(ded_compositefont_t * newDef)244 void CompositeBitmapFont::setDefinition(ded_compositefont_t *newDef)
245 {
246     d->def = newDef;
247 }
248 
rebuildFromDef(ded_compositefont_t const & newDef)249 void CompositeBitmapFont::rebuildFromDef(ded_compositefont_t const &newDef)
250 {
251     LOG_AS("CompositeBitmapFont::rebuildFromDef");
252 
253     setDefinition(const_cast<ded_compositefont_t *>(&newDef));
254     if(!d->def) return;
255 
256     for(int i = 0; i < d->def->charMap.size(); ++i)
257     {
258         if(!d->def->charMap[i].path) continue;
259 
260         try
261         {
262             String glyphPatchPath = d->def->charMap[i].path->resolved();
263             glyphSetPatch(d->def->charMap[i].ch, glyphPatchPath);
264         }
265         catch(de::Uri::ResolveError const& er)
266         {
267             LOG_RES_WARNING(er.asText());
268         }
269     }
270 }
271