/* Copyright (c) 2013 yvt This file is part of OpenSpades. OpenSpades is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OpenSpades is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenSpades. If not, see . */ #include #include #include FT_FREETYPE_H #include FT_BITMAP_H #include "FTFont.h" #include #include #include #include #include #include namespace spades { namespace ngclient { namespace { struct FreeType { FT_Library library; FreeType() { FT_Init_FreeType(&library); } ~FreeType() { FT_Done_FreeType(library); } operator FT_Library() const { return library; } }; AutoDeletedThreadLocalStorage ft; FT_Library GetFreeType() { if (ft == nullptr) { ft = new FreeType(); } return *ft; } }; struct FTFaceWrapper { FT_Face face; std::string buffer; FTFaceWrapper(FT_Face f) : face(f) {} ~FTFaceWrapper() { FT_Done_Face(face); } operator FT_Face() const { return face; } }; struct FTBitmapWrapper { FT_Bitmap b; FTBitmapWrapper() { FT_Bitmap_New(&b); } ~FTBitmapWrapper() { FT_Bitmap_Done(GetFreeType(), &b); } operator FT_Bitmap *() { return &b; } FT_Bitmap *operator->() { return &b; } }; FTFontSet::FTFontSet() { SPADES_MARK_FUNCTION(); } FTFontSet::~FTFontSet() { SPADES_MARK_FUNCTION(); } void FTFontSet::AddFace(const std::string &fileName) { FT_Face face; std::string data = FileManager::ReadAllBytes(fileName.c_str()); auto ret = FT_New_Memory_Face( GetFreeType(), reinterpret_cast(data.data()), data.size(), 0, &face); if (ret) { SPRaise("Failed to load font %s: FreeType error %d", fileName.c_str(), ret); } auto *wr = new FTFaceWrapper(face); wr->buffer = std::move(data); faces.emplace_back(wr); } struct BinPlaceResult { client::IImage ℑ int x, y; BinPlaceResult(client::IImage &image, int x, int y) : image(image), x(x), y(y) {} }; struct FTFont::Bin { int const width, height; Handle image; std::list> skyline; Bin(int width, int height, client::IRenderer &r) : width(width), height(height) { Handle tmpbmp(new Bitmap(width, height), false); memset(tmpbmp->GetPixels(), 0, tmpbmp->GetWidth() * tmpbmp->GetHeight() * 4); image.Set(r.CreateImage(tmpbmp), false); skyline.emplace_back(0, 0); skyline.emplace_back(width, 0); } stmp::optional Place(Bitmap &bmp) { int bw = bmp.GetWidth(), bh = bmp.GetHeight(); if (bw > width || bh > height) { SPRaise("Impossible to allocate bitmap of " "%dx%d on %dx%d bin.", bw, bh, width, height); } auto it = skyline.begin(); auto it2 = it; ++it2; auto bestIt1 = it, bestIt2 = it2; int bestWasted = std::numeric_limits::max(); int bestMaxY = 0; while (it2 != skyline.end() && it != skyline.end()) { while (it2 != skyline.end() && it2->first < it->first + bw) { ++it2; } if (it2 == skyline.end()) { break; } auto maxY = std::max_element(it, it2, [](const std::pair &a, const std::pair &b) { return a.second < b.second; })->second; if (maxY + bh < height) { int right = it->first + bw; int wasted = 0; for (auto it1 = it; it1 != it2;) { auto cur = it1++; auto segRight = it1->first; wasted += std::min(segRight, right) * (maxY - cur->second); } if (wasted < bestWasted) { bestIt1 = it; bestIt2 = it2; bestWasted = wasted; bestMaxY = maxY; } } ++it; } if (bestWasted == std::numeric_limits::max()) { // failed return stmp::optional(); } SPAssert(bestIt1 != bestIt2); int right = bestIt1->first + bw; BinPlaceResult result(*image, bestIt1->first, bestMaxY); if (bestIt2->first == right) { it = bestIt1; ++it; skyline.erase(it, bestIt2); bestIt1->second = bestMaxY + bh; } else { it = bestIt2; --it; if (it == bestIt1) { it = skyline.emplace(bestIt2, right, bestIt1->second); --bestIt2; } else { --bestIt2; bestIt2->first = right; } it = bestIt1; ++it; skyline.erase(it, bestIt2); bestIt1->second = bestMaxY + bh; } image->Update(bmp, result.x, result.y); return result; } }; FTFont::FTFont(client::IRenderer *renderer, FTFontSet *fontSet, float height, float lineHeight) : client::IFont(renderer), renderer(renderer), lineHeight(lineHeight), height(height), fontSet(fontSet) { SPADES_MARK_FUNCTION(); SPAssert(renderer); SPAssert(fontSet); binSize = 256; int targetSize = static_cast(std::min(height * 4.f, 4000.f)); while (binSize < targetSize) { binSize <<= 1; } baselineY = std::floor(height * 1.f); rendererIsLowQuality = dynamic_cast(renderer); bins.emplace_back(binSize, binSize, *renderer); } FTFont::~FTFont() { SPADES_MARK_FUNCTION(); } FTFont::Glyph *FTFont::GetGlyph(uint32_t code) { auto it = glyphMap.find(code); if (it != glyphMap.end()) { auto ref = it->second; return &ref.get(); } for (const auto &face : fontSet->faces) { auto cId = FT_Get_Char_Index(*face, code); if (cId != 0) { auto it2 = glyphs.find(std::make_pair(*face, cId)); if (it2 == glyphs.end()) { FT_Set_Char_Size(*face, 0, static_cast(height * 64.f), 72, 72); Glyph g; g.face = *face; g.charIndex = cId; FT_Load_Glyph(*face, cId, FT_LOAD_NO_HINTING); const auto &adv = g.face->glyph->advance; g.advance = Vector2(adv.x, adv.y) / (64.f); auto it3 = glyphs.emplace(std::make_pair(*face, cId), std::move(g)); glyphMap.emplace(code, it3.first->second); return &it3.first->second; } else { return &it2->second; } } } return nullptr; } template void FTFont::SplitTextIntoGlyphs(const std::string &str, T onGlyph, T3 onFallback, T2 onLineBreak) { // FIXME: ligatures? std::size_t i = 0; uint32_t breakCode = 0; while (i < str.size()) { std::size_t advance; auto code = GetCodePointFromUTF8String(str, i, &advance); i += advance; if (code == 10 || code == 13) { if (breakCode == 0) breakCode = code; if (code == breakCode) { onLineBreak(); } continue; } auto *g = GetGlyph(code); if (g) { onGlyph(*g); } else { onFallback(code); } } } Vector2 FTFont::Measure(const std::string &str) { SPADES_MARK_FUNCTION(); float maxWidth = 0.f; float x = 0.f; int lines = 1; SplitTextIntoGlyphs(str, [&](Glyph &g) { x += g.advance.x; maxWidth = std::max(x, maxWidth); }, [&](uint32_t codepoint) { x += MeasureFallback(codepoint, height); maxWidth = std::max(x, maxWidth); }, [&]() { ++lines; x = 0.f; }); return Vector2(maxWidth, lines * lineHeight); } void FTFont::RenderGlyph(Glyph &g) { if (g.image) return; FT_Set_Char_Size(g.face, 0, static_cast(height * 64.f), 72, 72); FT_Load_Glyph(g.face, g.charIndex, FT_LOAD_NO_HINTING); FT_Render_Glyph(g.face->glyph, FT_RENDER_MODE_NORMAL); auto &bmp = g.face->glyph->bitmap; FTBitmapWrapper outbmp; FT_Bitmap_Convert(GetFreeType(), &bmp, outbmp, 1); SPAssert(outbmp->pixel_mode == FT_PIXEL_MODE_GRAY); Handle spbmp(new Bitmap(outbmp->width + 1, outbmp->rows + 1), false); memset(spbmp->GetPixels(), 0, 4 * spbmp->GetWidth() * spbmp->GetHeight()); for (int y = 0; y < outbmp->rows; ++y) { const auto *inpixs = outbmp->buffer + y * outbmp->width; auto *outpixs = spbmp->GetPixels(); outpixs += y * spbmp->GetWidth(); for (int x = 0; x < outbmp->width; ++x) { uint32_t v = *inpixs; v = (v << 24) | 0xffffff; *outpixs = v; outpixs++; inpixs++; } } // allocate bin auto result = bins.back().Place(*spbmp); if (!result) { // bin full bins.emplace_back(binSize, binSize, *renderer); result = bins.back().Place(*spbmp); SPAssert(result); } AABB2 bounds((*result).x, (*result).y, spbmp->GetWidth() - 1, spbmp->GetHeight() - 1); Vector2 offs(g.face->glyph->bitmap_left, baselineY - g.face->glyph->bitmap_top); g.image.reset((*result).image, bounds, offs); g.bmp = spbmp; } void FTFont::RenderBlurGlyph(Glyph &g) { RenderGlyph(g); if (g.blurImage) return; enum { KernelSize = 6 }; auto &orig = *g.bmp; Handle newbmp( new Bitmap(orig.GetWidth() + KernelSize, orig.GetHeight() + KernelSize), false); int const origW = orig.GetWidth(); int const origH = orig.GetHeight(); int const newW = newbmp->GetWidth(); int const newH = newbmp->GetHeight(); std::vector buf; buf.resize(newW + KernelSize); for (int y = 0; y < newH; ++y) { int iy = y - (KernelSize >> 1); auto *pixels = newbmp->GetPixels() + y * newW; if (iy >= 0 && iy < origH) { { auto *inp = orig.GetPixels() + iy * origW; auto *outp = buf.data() + KernelSize; for (int x = 0; x < origW; ++x) { *(outp++) = *(inp++) >> 24; } } for (int x = 0; x < newW; ++x) { uint32_t sum; sum = static_cast(buf[x]) * 3590U; sum += static_cast(buf[x + 1]) * 8121U; sum += static_cast(buf[x + 2]) * 13254U; sum += static_cast(buf[x + 3]) * 15606U; sum += static_cast(buf[x + 4]) * 13254U; sum += static_cast(buf[x + 5]) * 8121U; sum += static_cast(buf[x + 6]) * 3590U; *(pixels++) = (sum >> 16 << 24) | 0xffffff; } } else { memset(pixels, 0, newW * 4); } } buf.resize(newH + KernelSize); for (auto &e : buf) e = 0; for (int x = 0; x < newW; ++x) { auto *pixels = newbmp->GetPixels() + x; { auto *inp = pixels; auto *outp = buf.data() + (KernelSize >> 1); for (int y = 0; y < newH; ++y) { *(outp++) = *(inp) >> 24; inp += newW; } } for (int y = 0; y < newH; ++y) { uint32_t sum; sum = static_cast(buf[y]) * 3590U; sum += static_cast(buf[y + 1]) * 8121U; sum += static_cast(buf[y + 2]) * 13254U; sum += static_cast(buf[y + 3]) * 15606U; sum += static_cast(buf[y + 4]) * 13254U; sum += static_cast(buf[y + 5]) * 8121U; sum += static_cast(buf[y + 6]) * 3590U; *(pixels) = (sum >> 16 << 24) | 0xffffff; pixels += newW; } } // allocate bin auto result = bins.back().Place(*newbmp); if (!result) { // bin full bins.emplace_back(binSize, binSize, *renderer); result = bins.back().Place(*newbmp); SPAssert(result); } AABB2 bounds((*result).x, (*result).y, newbmp->GetWidth(), newbmp->GetHeight()); Vector2 offs = (*g.image).offset - Vector2(1, 1) * (KernelSize * 0.5f); g.blurImage.reset((*result).image, bounds, offs); } void FTFont::Draw(const std::string &str, Vector2 offset, float scale, Vector4 color) { SPADES_MARK_FUNCTION(); auto lines = SplitIntoLines(str); float x = 0.f; float y = 0.f; color = Vector4(color.x * color.w, color.y * color.w, color.z * color.w, color.w); renderer->SetColorAlphaPremultiplied(color); SplitTextIntoGlyphs( str, [&](Glyph &g) { RenderGlyph(g); auto &img = *g.image; auto srcBounds = img.bounds; auto target = offset + (Vector2(x, y) + img.offset) * scale; target = (target + .5f).Floor(); // for sharper rendering AABB2 destBounds(target.x, target.y, srcBounds.GetWidth() * scale, srcBounds.GetHeight() * scale); if (!rendererIsLowQuality) { srcBounds = srcBounds.Inflate(.5f); destBounds = destBounds.Inflate(.5f * scale); } renderer->DrawImage(&img.img, destBounds, srcBounds); x += g.advance.x; y += g.advance.y; }, [&](uint32_t codepoint) { DrawFallback(codepoint, offset + Vector2(x, y) * scale, height * scale, color); x += MeasureFallback(codepoint, height); }, [&]() { x = 0.f; y += lineHeight; }); } void FTFont::DrawBlurred(const std::string &str, Vector2 offset, float scale, Vector4 color) { SPADES_MARK_FUNCTION(); auto lines = SplitIntoLines(str); float x = 0.f; float y = 0.f; color = Vector4(color.x * color.w, color.y * color.w, color.z * color.w, color.w); renderer->SetColorAlphaPremultiplied(color); SplitTextIntoGlyphs( str, [&](Glyph &g) { RenderBlurGlyph(g); auto &img = *g.blurImage; auto srcBounds = img.bounds; auto target = offset + (Vector2(x, y) + img.offset) * scale; target = (target + .5f).Floor(); // for sharper rendering AABB2 destBounds(target.x, target.y, srcBounds.GetWidth() * scale, srcBounds.GetHeight() * scale); if (!rendererIsLowQuality) { srcBounds = srcBounds.Inflate(.5f); destBounds = destBounds.Inflate(.5f * scale); } renderer->DrawImage(&img.img, destBounds, srcBounds); x += g.advance.x; y += g.advance.y; }, [&](uint32_t codepoint) { DrawFallback(codepoint, offset + Vector2(x, y) * scale, height * scale, color); x += MeasureFallback(codepoint, height); }, [&]() { x = 0.f; y += lineHeight; }); } void FTFont::DrawShadow(const std::string &text, const Vector2 &offset, float scale, const Vector4 &color, const Vector4 &shadowColor) { DrawBlurred(text, offset, scale, shadowColor); Draw(text, offset, scale, color); } } }