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
21namespace os {
22
23gfx::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
32retry:;
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