1 | // LAF OS Library |
2 | // Copyright (C) 2020-2022 Igara Studio S.A. |
3 | // Copyright (C) 2017 David Capello |
4 | // |
5 | // This file is released under the terms of the MIT license. |
6 | // Read LICENSE.txt for more information. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "os/draw_text.h" |
13 | |
14 | #include "ft/algorithm.h" |
15 | #include "ft/hb_shaper.h" |
16 | #include "gfx/clip.h" |
17 | #include "os/common/freetype_font.h" |
18 | #include "os/common/generic_surface.h" |
19 | #include "os/common/sprite_sheet_font.h" |
20 | |
21 | namespace os { |
22 | |
23 | gfx::Rect draw_text(Surface* surface, Font* font, |
24 | const std::string& text, |
25 | gfx::Color fg, gfx::Color bg, |
26 | int x, int y, |
27 | DrawTextDelegate* delegate) |
28 | { |
29 | base::utf8_decode decode(text); |
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 | while (uint32_t code = decode.next()) { |
39 | if (code && !font->hasCodePoint(code)) { |
40 | Font* newFont = font->fallback(); |
41 | |
42 | // Search a valid fallback |
43 | while (newFont && !newFont->hasCodePoint(code)) |
44 | newFont = newFont->fallback(); |
45 | if (!newFont) |
46 | break; |
47 | |
48 | y += font->height()/2 - newFont->height()/2; |
49 | |
50 | font = newFont; |
51 | goto retry; |
52 | } |
53 | } |
54 | } |
55 | |
56 | switch (font->type()) { |
57 | |
58 | case FontType::Unknown: |
59 | // Do nothing |
60 | break; |
61 | |
62 | case FontType::SpriteSheet: { |
63 | SpriteSheetFont* ssFont = static_cast<SpriteSheetFont*>(font); |
64 | Surface* sheet = ssFont->sheetSurface(); |
65 | |
66 | if (surface) { |
67 | sheet->lock(); |
68 | surface->lock(); |
69 | } |
70 | |
71 | decode = base::utf8_decode(text); |
72 | while (true) { |
73 | const int i = decode.pos() - text.begin(); |
74 | const int chr = decode.next(); |
75 | if (!chr) |
76 | break; |
77 | |
78 | gfx::Rect charBounds = ssFont->getCharBounds(chr); |
79 | gfx::Rect outCharBounds(x, y, charBounds.w, charBounds.h); |
80 | |
81 | if (delegate) |
82 | delegate->preProcessChar(i, chr, fg, bg, outCharBounds); |
83 | |
84 | if (delegate && !delegate->preDrawChar(outCharBounds)) |
85 | break; |
86 | |
87 | if (!charBounds.isEmpty()) { |
88 | if (surface) |
89 | surface->drawColoredRgbaSurface(sheet, fg, bg, gfx::Clip(x, y, charBounds)); |
90 | } |
91 | |
92 | textBounds |= outCharBounds; |
93 | if (delegate) |
94 | delegate->postDrawChar(outCharBounds); |
95 | |
96 | x += charBounds.w; |
97 | } |
98 | |
99 | if (surface) { |
100 | surface->unlock(); |
101 | sheet->unlock(); |
102 | } |
103 | break; |
104 | } |
105 | |
106 | case FontType::FreeType: { |
107 | FreeTypeFont* ttFont = static_cast<FreeTypeFont*>(font); |
108 | int fg_alpha = gfx::geta(fg); |
109 | |
110 | gfx::Rect clipBounds; |
111 | os::SurfaceFormatData fd; |
112 | if (surface) { |
113 | clipBounds = surface->getClipBounds(); |
114 | surface->getFormat(&fd); |
115 | surface->lock(); |
116 | } |
117 | |
118 | ft::ForEachGlyph<FreeTypeFont::Face> feg(ttFont->face(), text); |
119 | while (feg.next()) { |
120 | gfx::Rect origDstBounds; |
121 | auto glyph = feg.glyph(); |
122 | if (glyph) |
123 | origDstBounds = gfx::Rect( |
124 | x + int(glyph->startX), |
125 | y + int(glyph->y), |
126 | int(glyph->endX) - int(glyph->startX), |
127 | int(glyph->bitmap->rows) ? int(glyph->bitmap->rows): 1); |
128 | |
129 | if (delegate) { |
130 | delegate->preProcessChar( |
131 | feg.charIndex(), |
132 | feg.unicodeChar(), |
133 | fg, bg, origDstBounds); |
134 | } |
135 | |
136 | if (!glyph) |
137 | continue; |
138 | |
139 | if (delegate && !delegate->preDrawChar(origDstBounds)) |
140 | break; |
141 | |
142 | origDstBounds.x = x + int(glyph->x); |
143 | origDstBounds.w = int(glyph->bitmap->width); |
144 | origDstBounds.h = int(glyph->bitmap->rows); |
145 | |
146 | gfx::Rect dstBounds = origDstBounds; |
147 | if (surface) |
148 | dstBounds &= clipBounds; |
149 | |
150 | if (surface && !dstBounds.isEmpty()) { |
151 | int clippedRows = dstBounds.y - origDstBounds.y; |
152 | int dst_y = dstBounds.y; |
153 | int t; |
154 | for (int v=0; v<dstBounds.h; ++v, ++dst_y) { |
155 | int bit = 0; |
156 | const uint8_t* p = glyph->bitmap->buffer |
157 | + (v+clippedRows)*glyph->bitmap->pitch; |
158 | int dst_x = dstBounds.x; |
159 | uint32_t* dst_address = |
160 | (uint32_t*)surface->getData(dst_x, dst_y); |
161 | |
162 | // TODO maybe if we are trying to draw in a SkiaSurface with a nullptr m_bitmap |
163 | // (when GPU-acceleration is enabled) |
164 | if (!dst_address) |
165 | break; |
166 | |
167 | // Skip first clipped pixels |
168 | for (int u=0; u<dstBounds.x-origDstBounds.x; ++u) { |
169 | if (glyph->bitmap->pixel_mode == FT_PIXEL_MODE_GRAY) { |
170 | ++p; |
171 | } |
172 | else if (glyph->bitmap->pixel_mode == FT_PIXEL_MODE_MONO) { |
173 | if (bit == 8) { |
174 | bit = 0; |
175 | ++p; |
176 | } |
177 | } |
178 | } |
179 | |
180 | for (int u=0; u<dstBounds.w; ++u, ++dst_x) { |
181 | ASSERT(clipBounds.contains(gfx::Point(dst_x, dst_y))); |
182 | |
183 | int alpha; |
184 | if (glyph->bitmap->pixel_mode == FT_PIXEL_MODE_GRAY) { |
185 | alpha = *(p++); |
186 | } |
187 | else if (glyph->bitmap->pixel_mode == FT_PIXEL_MODE_MONO) { |
188 | alpha = ((*p) & (1 << (7 - (bit++))) ? 255: 0); |
189 | if (bit == 8) { |
190 | bit = 0; |
191 | ++p; |
192 | } |
193 | } |
194 | |
195 | uint32_t backdrop = *dst_address; |
196 | gfx::Color backdropColor = |
197 | gfx::rgba( |
198 | ((backdrop & fd.redMask) >> fd.redShift), |
199 | ((backdrop & fd.greenMask) >> fd.greenShift), |
200 | ((backdrop & fd.blueMask) >> fd.blueShift), |
201 | ((backdrop & fd.alphaMask) >> fd.alphaShift)); |
202 | |
203 | gfx::Color output = gfx::rgba(gfx::getr(fg), |
204 | gfx::getg(fg), |
205 | gfx::getb(fg), |
206 | MUL_UN8(fg_alpha, alpha, t)); |
207 | if (gfx::geta(bg) > 0) |
208 | output = blend(blend(backdropColor, bg), output); |
209 | else |
210 | output = blend(backdropColor, output); |
211 | |
212 | *dst_address = |
213 | ((gfx::getr(output) << fd.redShift ) & fd.redMask ) | |
214 | ((gfx::getg(output) << fd.greenShift) & fd.greenMask) | |
215 | ((gfx::getb(output) << fd.blueShift ) & fd.blueMask ) | |
216 | ((gfx::geta(output) << fd.alphaShift) & fd.alphaMask); |
217 | |
218 | ++dst_address; |
219 | } |
220 | } |
221 | } |
222 | |
223 | if (!origDstBounds.w) origDstBounds.w = 1; |
224 | if (!origDstBounds.h) origDstBounds.h = 1; |
225 | textBounds |= origDstBounds; |
226 | if (delegate) |
227 | delegate->postDrawChar(origDstBounds); |
228 | } |
229 | |
230 | if (surface) |
231 | surface->unlock(); |
232 | break; |
233 | } |
234 | |
235 | } |
236 | |
237 | return textBounds; |
238 | } |
239 | |
240 | } // namespace os |
241 | |