1 /*
2 *
3 * Copyright 2019, The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 #include <teeui/font_rendering.h>
19
20 namespace teeui {
21
isBreakable(unsigned long codePoint)22 bool isBreakable(unsigned long codePoint) {
23 switch (codePoint) {
24 case 9:
25 case 0xA:
26 case 0xB:
27 case 0xC:
28 case 0xD:
29 case 0x20:
30 case 0x85:
31 case 0x1680: // Ogham Space Mark
32 case 0x180E: // Mongolian Vowel Separator
33 case 0x2000: // EN Quad
34 case 0x2001: // EM Quad
35 case 0x2002: // EN Space
36 case 0x2003: // EM Space
37 case 0x2004: // three per em space
38 case 0x2005: // four per em space
39 case 0x2006: // six per em space
40 case 0x2008: // Punctuation space
41 case 0x2009: // Thin Space
42 case 0x200A: // Hair Space
43 case 0x200B: // Zero Width Space
44 case 0x200C: // Zero Width Non-Joiner
45 case 0x200D: // Zero Width Joiner
46 case 0x2028: // Line Separator
47 case 0x2029: // Paragraph Separator
48 case 0x205F: // Medium Mathematical Space
49 case 0x3000: // Ideographic Space
50 return true;
51 default:
52 return false;
53 }
54 }
55
setCharSize(signed long char_size,unsigned int dpi)56 Error TextFace::setCharSize(signed long char_size, unsigned int dpi) {
57 if (!face_) return Error::NotInitialized;
58 auto error = FT_Set_Char_Size(*face_, 0, char_size, 0, dpi);
59 if (error) return Error::CharSizeNotSet;
60 return Error::OK;
61 }
62
setCharSizeInPix(pxs size)63 Error TextFace::setCharSizeInPix(pxs size) {
64 if (!face_) return Error::NotInitialized;
65 if (FT_Set_Pixel_Sizes(*face_, 0, size.count())) {
66 return Error::CharSizeNotSet;
67 }
68 return Error::OK;
69 }
70
getCharIndex(unsigned long codePoint)71 GlyphIndex TextFace::getCharIndex(unsigned long codePoint) {
72 if (!face_) return 0;
73 return FT_Get_Char_Index(*face_, codePoint);
74 }
75
loadGlyph(GlyphIndex index)76 Error TextFace::loadGlyph(GlyphIndex index) {
77 if (!face_) return Error::NotInitialized;
78 if (FT_Load_Glyph(*face_, index, FT_LOAD_DEFAULT)) {
79 return Error::GlyphNotLoaded;
80 }
81 return Error::OK;
82 }
83
renderGlyph()84 Error TextFace::renderGlyph() {
85 if (!face_) return Error::NotInitialized;
86 if (FT_Render_Glyph(face_->glyph, FT_RENDER_MODE_NORMAL)) {
87 return Error::GlyphNotRendered;
88 }
89 return Error::OK;
90 }
91
advance() const92 Vec2d<pxs> TextFace::advance() const {
93 return Vec2d<pxs>(face_->glyph->advance.x / 64.0, face_->glyph->advance.y / 64.0);
94 }
95
kern(GlyphIndex previous) const96 Vec2d<pxs> TextFace::kern(GlyphIndex previous) const {
97 FT_Vector offset = {0, 0};
98 if (hasKerning_ && previous) {
99 if (!FT_Get_Kerning(*face_, previous, face_->glyph->glyph_index, FT_KERNING_DEFAULT,
100 &offset)) {
101 offset = {0, 0};
102 }
103 }
104 return {offset.x / 64.0, offset.y / 64.0};
105 }
106
getGlyphBBox() const107 optional<Box<pxs>> TextFace::getGlyphBBox() const {
108 FT_Glyph glyph;
109 if (!FT_Get_Glyph(face_->glyph, &glyph)) {
110 FT_BBox cbox;
111 FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_pixels, &cbox);
112 FT_Done_Glyph(glyph);
113 return {{cbox.xMin, -cbox.yMax, cbox.xMax - cbox.xMin, cbox.yMax - cbox.yMin}};
114 }
115 return {};
116 }
117
create()118 std::tuple<Error, TextContext> TextContext::create() {
119 std::tuple<Error, TextContext> result;
120 auto& [rc, lib] = result;
121 rc = Error::NotInitialized;
122 FT_Library library = nullptr;
123 auto error = FT_Init_FreeType(&library);
124 if (error) return result;
125 rc = Error::OK;
126 lib.library_ = Handle(library);
127 return result;
128 }
129
130 std::tuple<Error, Box<pxs>, UTF8Range<const char*>>
findLongestWordSequence(TextFace * face,const UTF8Range<const char * > & text,const Box<pxs> & boundingBox)131 findLongestWordSequence(TextFace* face, const UTF8Range<const char*>& text,
132 const Box<pxs>& boundingBox) {
133 std::tuple<Error, Box<pxs>, UTF8Range<const char*>> result;
134 auto& [error, bBox, resultRange] = result;
135
136 Vec2d<pxs> pen = {0, 0};
137 bBox = {pen, {0, 0}};
138
139 GlyphIndex previous = 0;
140 UTF8WordRange<const char*> wordRange(text);
141 auto rangeBegin = wordRange.begin();
142 if (isBreakable((*rangeBegin).codePoint())) {
143 ++rangeBegin;
144 }
145 auto wordEnd = rangeBegin;
146 auto wordStart = wordEnd;
147 ++wordEnd;
148 auto sequenceEnd = *wordStart;
149 auto lastBreakableBegin = *wordStart;
150 Box<pxs> currentBox = bBox;
151 Box<pxs> currentFullWordBox = bBox;
152 while (wordStart != wordRange.end()) {
153 auto codePoint = UTF8Range<const char*>::codePoint(**wordStart);
154 if (isBreakable(codePoint)) {
155 lastBreakableBegin = *wordStart;
156 currentFullWordBox = currentBox;
157 }
158
159 Box<pxs> workingBox = currentBox;
160 auto c = *wordStart;
161 bool exceedsBoundingBox = false;
162 while (c != *wordEnd) {
163 codePoint = c.codePoint();
164 auto gindex = face->getCharIndex(codePoint);
165 if (gindex == 0) {
166 error = Error::GlyphNotLoaded;
167 return result;
168 }
169 error = face->loadGlyph(gindex);
170 if (error != Error::OK) return result;
171 pen += face->kern(previous);
172 if (auto gBox = face->getGlyphBBox()) {
173 TEEUI_LOG << "Glyph Box: " << *gBox << ENDL;
174 workingBox = workingBox.merge(gBox->translateSelf(pen));
175 TEEUI_LOG << "WorkingBox: " << workingBox << ENDL;
176 } else {
177 error = Error::BBoxComputation;
178 return result;
179 }
180 pen += face->advance();
181 previous = gindex;
182 ++c;
183 if (workingBox.fitsInside(boundingBox)) {
184 currentBox = workingBox;
185 sequenceEnd = c;
186 } else {
187 exceedsBoundingBox = true;
188 TEEUI_LOG << "exceeding bbox" << ENDL;
189 break;
190 }
191 }
192 if (exceedsBoundingBox) break;
193 wordStart = wordEnd;
194 ++wordEnd;
195 }
196 if (wordStart == wordRange.end()) {
197 bBox = currentBox;
198 resultRange = {**rangeBegin, *text.end()};
199 TEEUI_LOG << "full range" << ENDL;
200 } else if (*rangeBegin != lastBreakableBegin) {
201 bBox = currentFullWordBox;
202 resultRange = {**rangeBegin, *lastBreakableBegin};
203 TEEUI_LOG << "partial range:" << ENDL;
204 } else {
205 bBox = currentBox;
206 resultRange = {**rangeBegin, *sequenceEnd};
207 TEEUI_LOG << "unbreakable" << ENDL;
208 }
209 error = Error::OK;
210 return result;
211 }
212
drawText(TextFace * face,const UTF8Range<const char * > & text,const PixelDrawer & drawPixel,PxPoint pen)213 Error drawText(TextFace* face, const UTF8Range<const char*>& text, const PixelDrawer& drawPixel,
214 PxPoint pen) {
215 Error error;
216
217 for (auto c : text) {
218 auto codePoint = UTF8Range<const char*>::codePoint(c);
219 auto gindex = face->getCharIndex(codePoint);
220 error = face->loadGlyph(gindex);
221 if (error == Error::OK) error = face->renderGlyph();
222 if (error == Error::OK) error = face->drawGlyph(pen, drawPixel);
223 if (error != Error::OK) return error;
224
225 pen += face->advance();
226 }
227 return Error::OK;
228 }
229
230 } // namespace teeui
231