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