1 // SHE library
2 // Copyright (C) 2017  David Capello
3 //
4 // This file is released under the terms of the MIT license.
5 // Read LICENSE.txt for more information.
6 
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10 
11 #include "she/draw_text.h"
12 
13 #include "ft/algorithm.h"
14 #include "ft/hb_shaper.h"
15 #include "gfx/clip.h"
16 #include "she/common/freetype_font.h"
17 #include "she/common/generic_surface.h"
18 #include "she/common/sprite_sheet_font.h"
19 
20 namespace she {
21 
draw_text(Surface * surface,Font * font,const base::utf8_const_iterator & begin,const base::utf8_const_iterator & end,gfx::Color fg,gfx::Color bg,int x,int y,DrawTextDelegate * delegate)22 gfx::Rect draw_text(Surface* surface, Font* font,
23                     const base::utf8_const_iterator& begin,
24                     const base::utf8_const_iterator& end,
25                     gfx::Color fg, gfx::Color bg,
26                     int x, int y,
27                     DrawTextDelegate* delegate)
28 {
29   base::utf8_const_iterator it = begin;
30   gfx::Rect textBounds;
31 
32 retry:;
33   // Check if this font is enough to draw the given string or we will
34   // need the fallback for some special Unicode chars
35   if (font->fallback()) {
36     // TODO compose unicode characters and check those codepoints, the
37     //      same in the drawing code of sprite sheet font
38     for (auto it=begin; it!=end; ++it) {
39       uint32_t code = *it;
40       if (code && !font->hasCodePoint(code)) {
41         Font* newFont = font->fallback();
42 
43         // Search a valid fallback
44         while (newFont && !newFont->hasCodePoint(code))
45           newFont = newFont->fallback();
46         if (!newFont)
47           break;
48 
49         y += font->height()/2 - newFont->height()/2;
50 
51         font = newFont;
52         goto retry;
53       }
54     }
55   }
56 
57   switch (font->type()) {
58 
59     case FontType::kSpriteSheet: {
60       SpriteSheetFont* ssFont = static_cast<SpriteSheetFont*>(font);
61       Surface* sheet = ssFont->getSurfaceSheet();
62 
63       if (surface) {
64         sheet->lock();
65         surface->lock();
66       }
67 
68       while (it != end) {
69         int chr = *it;
70         if (delegate) {
71           int i = it-begin;
72           delegate->preProcessChar(i, chr, fg, bg);
73         }
74 
75         gfx::Rect charBounds = ssFont->getCharBounds(chr);
76         gfx::Rect outCharBounds(x, y, charBounds.w, charBounds.h);
77         if (delegate && !delegate->preDrawChar(outCharBounds))
78           break;
79 
80         if (!charBounds.isEmpty()) {
81           if (surface)
82             surface->drawColoredRgbaSurface(sheet, fg, bg, gfx::Clip(x, y, charBounds));
83         }
84 
85         textBounds |= outCharBounds;
86         if (delegate)
87           delegate->postDrawChar(outCharBounds);
88 
89         x += charBounds.w;
90         ++it;
91       }
92 
93       if (surface) {
94         surface->unlock();
95         sheet->unlock();
96       }
97       break;
98     }
99 
100     case FontType::kTrueType: {
101       FreeTypeFont* ttFont = static_cast<FreeTypeFont*>(font);
102       bool antialias = ttFont->face().antialias();
103       int fg_alpha = gfx::geta(fg);
104 
105       gfx::Rect clipBounds;
106       she::SurfaceFormatData fd;
107       if (surface) {
108         clipBounds = surface->getClipBounds();
109         surface->getFormat(&fd);
110         surface->lock();
111       }
112 
113       ft::ForEachGlyph<FreeTypeFont::Face> feg(ttFont->face());
114       if (feg.initialize(it, end)) {
115         do {
116           if (delegate) {
117             delegate->preProcessChar(feg.charIndex(),
118                                      feg.unicodeChar(), fg, bg);
119           }
120 
121           auto glyph = feg.glyph();
122           if (!glyph)
123             continue;
124 
125           gfx::Rect origDstBounds(
126             x + int(glyph->startX),
127             y + int(glyph->y),
128             int(glyph->endX) - int(glyph->startX),
129             int(glyph->bitmap->rows) ? int(glyph->bitmap->rows): 1);
130 
131           if (delegate && !delegate->preDrawChar(origDstBounds))
132             break;
133 
134           origDstBounds.x = x + int(glyph->x);
135           origDstBounds.w = int(glyph->bitmap->width);
136           origDstBounds.h = int(glyph->bitmap->rows);
137 
138           gfx::Rect dstBounds = origDstBounds;
139           if (surface)
140             dstBounds &= clipBounds;
141 
142           if (surface && !dstBounds.isEmpty()) {
143             int clippedRows = dstBounds.y - origDstBounds.y;
144             int dst_y = dstBounds.y;
145             int t;
146             for (int v=0; v<dstBounds.h; ++v, ++dst_y) {
147               int bit = 0;
148               const uint8_t* p = glyph->bitmap->buffer
149                 + (v+clippedRows)*glyph->bitmap->pitch;
150               int dst_x = dstBounds.x;
151               uint32_t* dst_address =
152                 (uint32_t*)surface->getData(dst_x, dst_y);
153 
154               // Skip first clipped pixels
155               for (int u=0; u<dstBounds.x-origDstBounds.x; ++u) {
156                 if (antialias) {
157                   ++p;
158                 }
159                 else {
160                   if (bit == 8) {
161                     bit = 0;
162                     ++p;
163                   }
164                 }
165               }
166 
167               for (int u=0; u<dstBounds.w; ++u, ++dst_x) {
168                 ASSERT(clipBounds.contains(gfx::Point(dst_x, dst_y)));
169 
170                 int alpha;
171                 if (antialias) {
172                   alpha = *(p++);
173                 }
174                 else {
175                   alpha = ((*p) & (1 << (7 - (bit++))) ? 255: 0);
176                   if (bit == 8) {
177                     bit = 0;
178                     ++p;
179                   }
180                 }
181 
182                 uint32_t backdrop = *dst_address;
183                 gfx::Color backdropColor =
184                   gfx::rgba(
185                     ((backdrop & fd.redMask) >> fd.redShift),
186                     ((backdrop & fd.greenMask) >> fd.greenShift),
187                     ((backdrop & fd.blueMask) >> fd.blueShift),
188                     // Backdrop color is always opaque (needed for
189                     // Allegro port because it doesn't contain the
190                     // alpha=255)
191                     255);
192 
193                 gfx::Color output = gfx::rgba(gfx::getr(fg),
194                                               gfx::getg(fg),
195                                               gfx::getb(fg),
196                                               MUL_UN8(fg_alpha, alpha, t));
197                 if (gfx::geta(bg) > 0)
198                   output = blend(blend(backdropColor, bg), output);
199                 else
200                   output = blend(backdropColor, output);
201 
202                 *dst_address =
203                   ((gfx::getr(output) << fd.redShift  ) & fd.redMask  ) |
204                   ((gfx::getg(output) << fd.greenShift) & fd.greenMask) |
205                   ((gfx::getb(output) << fd.blueShift ) & fd.blueMask ) |
206                   ((gfx::geta(output) << fd.alphaShift) & fd.alphaMask);
207 
208                 ++dst_address;
209               }
210             }
211           }
212 
213           if (!origDstBounds.w) origDstBounds.w = 1;
214           if (!origDstBounds.h) origDstBounds.h = 1;
215           textBounds |= origDstBounds;
216           if (delegate)
217             delegate->postDrawChar(origDstBounds);
218         } while (feg.nextChar());
219       }
220 
221       if (surface)
222         surface->unlock();
223       break;
224     }
225 
226   }
227 
228   return textBounds;
229 }
230 
231 } // namespace she
232