/* * * Copyright 2019, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include namespace teeui { bool isBreakable(unsigned long codePoint) { switch (codePoint) { case 9: case 0xA: case 0xB: case 0xC: case 0xD: case 0x20: case 0x85: case 0x1680: // Ogham Space Mark case 0x180E: // Mongolian Vowel Separator case 0x2000: // EN Quad case 0x2001: // EM Quad case 0x2002: // EN Space case 0x2003: // EM Space case 0x2004: // three per em space case 0x2005: // four per em space case 0x2006: // six per em space case 0x2008: // Punctuation space case 0x2009: // Thin Space case 0x200A: // Hair Space case 0x200B: // Zero Width Space case 0x200C: // Zero Width Non-Joiner case 0x200D: // Zero Width Joiner case 0x2028: // Line Separator case 0x2029: // Paragraph Separator case 0x205F: // Medium Mathematical Space case 0x3000: // Ideographic Space return true; default: return false; } } Error TextFace::setCharSize(signed long char_size, unsigned int dpi) { if (!face_) return Error::NotInitialized; auto error = FT_Set_Char_Size(*face_, 0, char_size, 0, dpi); if (error) return Error::CharSizeNotSet; return Error::OK; } Error TextFace::setCharSizeInPix(pxs size) { if (!face_) return Error::NotInitialized; if (FT_Set_Pixel_Sizes(*face_, 0, size.count())) { return Error::CharSizeNotSet; } return Error::OK; } GlyphIndex TextFace::getCharIndex(unsigned long codePoint) { if (!face_) return 0; return FT_Get_Char_Index(*face_, codePoint); } Error TextFace::loadGlyph(GlyphIndex index) { if (!face_) return Error::NotInitialized; if (FT_Load_Glyph(*face_, index, FT_LOAD_DEFAULT)) { return Error::GlyphNotLoaded; } return Error::OK; } Error TextFace::renderGlyph() { if (!face_) return Error::NotInitialized; if (FT_Render_Glyph(face_->glyph, FT_RENDER_MODE_NORMAL)) { return Error::GlyphNotRendered; } return Error::OK; } Vec2d TextFace::advance() const { return Vec2d(face_->glyph->advance.x / 64.0, face_->glyph->advance.y / 64.0); } Vec2d TextFace::kern(GlyphIndex previous) const { FT_Vector offset = {0, 0}; if (hasKerning_ && previous) { if (!FT_Get_Kerning(*face_, previous, face_->glyph->glyph_index, FT_KERNING_DEFAULT, &offset)) { offset = {0, 0}; } } return {offset.x / 64.0, offset.y / 64.0}; } optional> TextFace::getGlyphBBox() const { FT_Glyph glyph; if (!FT_Get_Glyph(face_->glyph, &glyph)) { FT_BBox cbox; FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_pixels, &cbox); FT_Done_Glyph(glyph); return {{cbox.xMin, -cbox.yMax, cbox.xMax - cbox.xMin, cbox.yMax - cbox.yMin}}; } return {}; } std::tuple TextContext::create() { std::tuple result; auto& [rc, lib] = result; rc = Error::NotInitialized; FT_Library library = nullptr; auto error = FT_Init_FreeType(&library); if (error) return result; rc = Error::OK; lib.library_ = Handle(library); return result; } std::tuple, UTF8Range> findLongestWordSequence(TextFace* face, const UTF8Range& text, const Box& boundingBox) { std::tuple, UTF8Range> result; auto& [error, bBox, resultRange] = result; Vec2d pen = {0, 0}; bBox = {pen, {0, 0}}; GlyphIndex previous = 0; UTF8WordRange wordRange(text); auto rangeBegin = wordRange.begin(); if (isBreakable((*rangeBegin).codePoint())) { ++rangeBegin; } auto wordEnd = rangeBegin; auto wordStart = wordEnd; ++wordEnd; auto sequenceEnd = *wordStart; auto lastBreakableBegin = *wordStart; Box currentBox = bBox; Box currentFullWordBox = bBox; while (wordStart != wordRange.end()) { auto codePoint = UTF8Range::codePoint(**wordStart); if (isBreakable(codePoint)) { lastBreakableBegin = *wordStart; currentFullWordBox = currentBox; } Box workingBox = currentBox; auto c = *wordStart; bool exceedsBoundingBox = false; while (c != *wordEnd) { codePoint = c.codePoint(); auto gindex = face->getCharIndex(codePoint); if (gindex == 0) { error = Error::GlyphNotLoaded; return result; } error = face->loadGlyph(gindex); if (error != Error::OK) return result; pen += face->kern(previous); if (auto gBox = face->getGlyphBBox()) { TEEUI_LOG << "Glyph Box: " << *gBox << ENDL; workingBox = workingBox.merge(gBox->translateSelf(pen)); TEEUI_LOG << "WorkingBox: " << workingBox << ENDL; } else { error = Error::BBoxComputation; return result; } pen += face->advance(); previous = gindex; ++c; if (workingBox.fitsInside(boundingBox)) { currentBox = workingBox; sequenceEnd = c; } else { exceedsBoundingBox = true; TEEUI_LOG << "exceeding bbox" << ENDL; break; } } if (exceedsBoundingBox) break; wordStart = wordEnd; ++wordEnd; } if (wordStart == wordRange.end()) { bBox = currentBox; resultRange = {**rangeBegin, *text.end()}; TEEUI_LOG << "full range" << ENDL; } else if (*rangeBegin != lastBreakableBegin) { bBox = currentFullWordBox; resultRange = {**rangeBegin, *lastBreakableBegin}; TEEUI_LOG << "partial range:" << ENDL; } else { bBox = currentBox; resultRange = {**rangeBegin, *sequenceEnd}; TEEUI_LOG << "unbreakable" << ENDL; } error = Error::OK; return result; } Error drawText(TextFace* face, const UTF8Range& text, const PixelDrawer& drawPixel, PxPoint pen) { Error error; for (auto c : text) { auto codePoint = UTF8Range::codePoint(c); auto gindex = face->getCharIndex(codePoint); error = face->loadGlyph(gindex); if (error == Error::OK) error = face->renderGlyph(); if (error == Error::OK) error = face->drawGlyph(pen, drawPixel); if (error != Error::OK) return error; pen += face->advance(); } return Error::OK; } } // namespace teeui