int TTFFont::getCharWidth(byte chr) const { GlyphCache::const_iterator glyphEntry = _glyphs.find(chr); if (glyphEntry == _glyphs.end()) return 0; else return glyphEntry->_value.advance; }
int TTFFont::getKerningOffset(uint32 left, uint32 right) const { if (!_hasKerning) return 0; assureCached(left); assureCached(right); FT_UInt leftGlyph, rightGlyph; GlyphCache::const_iterator glyphEntry; glyphEntry = _glyphs.find(left); if (glyphEntry != _glyphs.end()) { leftGlyph = glyphEntry->_value.slot; } else { return 0; } glyphEntry = _glyphs.find(right); if (glyphEntry != _glyphs.end()) { rightGlyph = glyphEntry->_value.slot; } else { return 0; } if (!leftGlyph || !rightGlyph) return 0; FT_Vector kerningVector; FT_Get_Kerning(_face, leftGlyph, rightGlyph, FT_KERNING_DEFAULT, &kerningVector); return (kerningVector.x / 64); }
Common::Rect TTFFont::getBoundingBox(uint32 chr) const { assureCached(chr); GlyphCache::const_iterator glyphEntry = _glyphs.find(chr); if (glyphEntry == _glyphs.end()) { return Common::Rect(); } else { const int xOffset = glyphEntry->_value.xOffset; const int yOffset = glyphEntry->_value.yOffset; const Graphics::Surface &image = glyphEntry->_value.image; return Common::Rect(xOffset, yOffset, xOffset + image.w, yOffset + image.h); } }
void FreeTypeFont::doFillGlyphCache(GlyphCache& cache, const std::vector<char32_t>& characters) #endif { /** @bug Crash when atlas is too small */ /* Get glyph codes from characters */ std::vector<FT_UInt> charIndices; charIndices.resize(characters.size()+1); charIndices[0] = 0; std::transform(characters.begin(), characters.end(), charIndices.begin()+1, [this](const char32_t c) { return FT_Get_Char_Index(ftFont, c); }); /* Remove duplicates (e.g. uppercase and lowercase mapped to same glyph) */ std::sort(charIndices.begin(), charIndices.end()); charIndices.erase(std::unique(charIndices.begin(), charIndices.end()), charIndices.end()); /* Sizes of all characters */ std::vector<Vector2i> charSizes; charSizes.reserve(charIndices.size()); for(FT_UInt c: charIndices) { CORRADE_INTERNAL_ASSERT_OUTPUT(FT_Load_Glyph(ftFont, c, FT_LOAD_DEFAULT) == 0); charSizes.push_back(Vector2i(ftFont->glyph->metrics.width, ftFont->glyph->metrics.height)/64); } /* Create texture atlas */ const std::vector<Rectanglei> charPositions = cache.reserve(charSizes); /* Render all characters to the atlas and create character map */ unsigned char* pixmap = new unsigned char[cache.textureSize().product()](); /** @todo Some better way for this */ #ifndef MAGNUM_TARGET_GLES2 Image2D image(ImageFormat::Red, ImageType::UnsignedByte, cache.textureSize(), pixmap); #else Image2D image(Context::current() && Context::current()->isExtensionSupported<Extensions::GL::EXT::texture_rg>() ? ImageFormat::Red : ImageFormat::Luminance, ImageType::UnsignedByte, cache.textureSize(), pixmap); #endif for(std::size_t i = 0; i != charPositions.size(); ++i) { /* Load and render glyph */ /** @todo B&W only if radius != 0 */ FT_GlyphSlot glyph = ftFont->glyph; CORRADE_INTERNAL_ASSERT_OUTPUT(FT_Load_Glyph(ftFont, charIndices[i], FT_LOAD_DEFAULT) == 0); CORRADE_INTERNAL_ASSERT_OUTPUT(FT_Render_Glyph(glyph, FT_RENDER_MODE_NORMAL) == 0); /* Copy rendered bitmap to texture image */ const FT_Bitmap& bitmap = glyph->bitmap; CORRADE_INTERNAL_ASSERT(std::abs(bitmap.width-charPositions[i].width()) <= 2); CORRADE_INTERNAL_ASSERT(std::abs(bitmap.rows-charPositions[i].height()) <= 2); for(Int yin = 0, yout = charPositions[i].bottom(), ymax = bitmap.rows; yin != ymax; ++yin, ++yout) for(Int xin = 0, xout = charPositions[i].left(), xmax = bitmap.width; xin != xmax; ++xin, ++xout) pixmap[yout*cache.textureSize().x() + xout] = bitmap.buffer[(bitmap.rows-yin-1)*bitmap.width + xin]; /* Insert glyph parameters into cache */ cache.insert(charIndices[i], Vector2i(glyph->bitmap_left, glyph->bitmap_top-charPositions[i].height()), charPositions[i]); } /* Set cache image */ cache.setImage({}, image); }
void TTFFont::assureCached(uint32 chr) const { if (!chr || !_allowLateCaching || _glyphs.contains(chr)) { return; } Glyph newGlyph; if (cacheGlyph(newGlyph, chr)) { _glyphs[chr] = newGlyph; } }
inline void server_add_char( GlyphCache & gly_cache, uint8_t cacheId, uint16_t cacheIndex , int16_t offset, int16_t baseline , uint16_t width, uint16_t height, const uint8_t * data) { FontChar fi(offset, baseline, width, height, 0); memcpy(fi.data.get(), data, fi.datasize()); LOG(LOG_INFO, "cacheId=%u cacheIndex=%u", cacheId, cacheIndex); gly_cache.set_glyph(std::move(fi), cacheId, cacheIndex); }
void TTFFont::drawChar(Surface *dst, byte chr, int x, int y, uint32 color) const { GlyphCache::const_iterator glyphEntry = _glyphs.find(chr); if (glyphEntry == _glyphs.end()) return; const Glyph &glyph = glyphEntry->_value; x += glyph.xOffset; y += glyph.yOffset; if (x > dst->w) return; if (y > dst->h) return; int w = glyph.image.w; int h = glyph.image.h; const uint8 *srcPos = (const uint8 *)glyph.image.getBasePtr(0, 0); // Make sure we are not drawing outside the screen bounds if (x < 0) { srcPos -= x; w += x; x = 0; } if (x + w > dst->w) w = dst->w - x; if (w <= 0) return; if (y < 0) { srcPos -= y * glyph.image.pitch; h += y; y = 0; } if (y + h > dst->h) h = dst->h - y; if (h <= 0) return; uint8 *dstPos = (uint8 *)dst->getBasePtr(x, y); if (dst->format.bytesPerPixel == 1) { for (int cy = 0; cy < h; ++cy) { uint8 *rDst = dstPos; const uint8 *src = srcPos; for (int cx = 0; cx < w; ++cx) { // We assume a 1Bpp mode is a color indexed mode, thus we can // not take advantage of anti-aliasing here. if (*src >= 0x80) *rDst = color; ++rDst; ++src; } dstPos += dst->pitch; srcPos += glyph.image.pitch; } } else if (dst->format.bytesPerPixel == 2) { renderGlyph<uint16>(dstPos, dst->pitch, srcPos, glyph.image.pitch, w, h, color, dst->format); } else if (dst->format.bytesPerPixel == 4) { renderGlyph<uint32>(dstPos, dst->pitch, srcPos, glyph.image.pitch, w, h, color, dst->format); } }
bool TTFFont::load(Common::SeekableReadStream &stream, int size, bool monochrome, const uint32 *mapping) { if (!g_ttf.isInitialized()) return false; _size = stream.size(); if (!_size) return false; _ttfFile = new uint8[_size]; assert(_ttfFile); if (stream.read(_ttfFile, _size) != _size) { delete[] _ttfFile; _ttfFile = 0; return false; } if (!g_ttf.loadFont(_ttfFile, _size, _face)) { delete[] _ttfFile; _ttfFile = 0; return false; } // We only support scalable fonts. if (!FT_IS_SCALABLE(_face)) { delete[] _ttfFile; _ttfFile = 0; g_ttf.closeFont(_face); return false; } // Check whether we have kerning support _hasKerning = (FT_HAS_KERNING(_face) != 0); if (FT_Set_Char_Size(_face, 0, size * 64, 0, 0)) { delete[] _ttfFile; _ttfFile = 0; return false; } _monochrome = monochrome; FT_Fixed yScale = _face->size->metrics.y_scale; _ascent = ftCeil26_6(FT_MulFix(_face->ascender, yScale)); _descent = ftCeil26_6(FT_MulFix(_face->descender, yScale)); _width = ftCeil26_6(FT_MulFix(_face->max_advance_width, _face->size->metrics.x_scale)); _height = _ascent - _descent + 1; if (!mapping) { // Load all ISO-8859-1 characters. for (uint i = 0; i < 256; ++i) { if (!cacheGlyph(_glyphs[i], _glyphSlots[i], i)) _glyphSlots[i] = 0; } } else { for (uint i = 0; i < 256; ++i) { const uint32 unicode = mapping[i] & 0x7FFFFFFF; const bool isRequired = (mapping[i] & 0x80000000) != 0; // Check whether loading an important glyph fails and error out if // that is the case. if (!cacheGlyph(_glyphs[i], _glyphSlots[i], unicode)) { _glyphSlots[i] = 0; if (isRequired) return false; } } } _initialized = (_glyphs.size() != 0); return _initialized; }
bool TTFFont::load(Common::SeekableReadStream &stream, int size, uint dpi, TTFRenderMode renderMode, const uint32 *mapping) { if (!g_ttf.isInitialized()) return false; _size = stream.size(); if (!_size) return false; _ttfFile = new uint8[_size]; assert(_ttfFile); if (stream.read(_ttfFile, _size) != _size) { delete[] _ttfFile; _ttfFile = 0; return false; } if (!g_ttf.loadFont(_ttfFile, _size, _face)) { delete[] _ttfFile; _ttfFile = 0; return false; } // We only support scalable fonts. if (!FT_IS_SCALABLE(_face)) { delete[] _ttfFile; _ttfFile = 0; g_ttf.closeFont(_face); return false; } // Check whether we have kerning support _hasKerning = (FT_HAS_KERNING(_face) != 0); if (FT_Set_Char_Size(_face, 0, size * 64, dpi, dpi)) { delete[] _ttfFile; _ttfFile = 0; return false; } switch (renderMode) { case kTTFRenderModeNormal: _loadFlags = FT_LOAD_TARGET_NORMAL; _renderMode = FT_RENDER_MODE_NORMAL; break; case kTTFRenderModeLight: _loadFlags = FT_LOAD_TARGET_LIGHT; _renderMode = FT_RENDER_MODE_LIGHT; break; case kTTFRenderModeMonochrome: _loadFlags = FT_LOAD_TARGET_MONO; _renderMode = FT_RENDER_MODE_MONO; break; } FT_Fixed yScale = _face->size->metrics.y_scale; _ascent = ftCeil26_6(FT_MulFix(_face->ascender, yScale)); _descent = ftCeil26_6(FT_MulFix(_face->descender, yScale)); _width = ftCeil26_6(FT_MulFix(_face->max_advance_width, _face->size->metrics.x_scale)); _height = _ascent - _descent + 1; if (!mapping) { // Allow loading of all unicode characters. _allowLateCaching = true; // Load all ISO-8859-1 characters. for (uint i = 0; i < 256; ++i) { if (!cacheGlyph(_glyphs[i], i)) { _glyphs.erase(i); } } } else { // We have a fixed map of characters do not load more later. _allowLateCaching = false; for (uint i = 0; i < 256; ++i) { const uint32 unicode = mapping[i] & 0x7FFFFFFF; const bool isRequired = (mapping[i] & 0x80000000) != 0; // Check whether loading an important glyph fails and error out if // that is the case. if (!cacheGlyph(_glyphs[i], unicode)) { _glyphs.erase(i); if (isRequired) return false; } } } _initialized = (_glyphs.size() != 0); return _initialized; }
std::vector<std::pair<std::string, Containers::Array<char>>> MagnumFontConverter::doExportFontToData(AbstractFont& font, GlyphCache& cache, const std::string& filename, const std::u32string& characters) const { Utility::Configuration configuration; configuration.setValue("version", 1); configuration.setValue("image", Utility::Directory::filename(filename) + ".tga"); configuration.setValue("originalImageSize", cache.textureSize()); configuration.setValue("padding", cache.padding()); configuration.setValue("fontSize", font.size()); configuration.setValue("ascent", font.ascent()); configuration.setValue("descent", font.descent()); configuration.setValue("lineHeight", font.lineHeight()); /* Compress glyph IDs so the glyphs are in consecutive array, glyph 0 should stay at position 0 */ std::unordered_map<UnsignedInt, UnsignedInt> glyphIdMap; glyphIdMap.reserve(cache.glyphCount()); glyphIdMap.emplace(0, 0); for(const std::pair<UnsignedInt, std::pair<Vector2i, Range2Di>>& glyph: cache) glyphIdMap.emplace(glyph.first, glyphIdMap.size()); /** @todo Save only glyphs contained in @p characters */ /* Inverse map from new glyph IDs to old ones */ std::vector<UnsignedInt> inverseGlyphIdMap(glyphIdMap.size()); for(const std::pair<UnsignedInt, UnsignedInt>& map: glyphIdMap) inverseGlyphIdMap[map.second] = map.first; /* Character->glyph map, map glyph IDs to new ones */ for(const char32_t c: characters) { Utility::ConfigurationGroup* group = configuration.addGroup("char"); const UnsignedInt glyphId = font.glyphId(c); group->setValue("unicode", c); /* Map old glyph ID to new, if not found, map to glyph 0 */ auto found = glyphIdMap.find(glyphId); group->setValue("glyph", found == glyphIdMap.end() ? 0 : glyphIdMap.at(glyphId)); } /* Save glyph properties in order which preserves their IDs, remove padding from the values so they aren't added twice when using the font later */ /** @todo Some better way to handle this padding stuff */ for(UnsignedInt oldGlyphId: inverseGlyphIdMap) { std::pair<Vector2i, Range2Di> glyph = cache[oldGlyphId]; Utility::ConfigurationGroup* group = configuration.addGroup("glyph"); group->setValue("advance", font.glyphAdvance(oldGlyphId)); group->setValue("position", glyph.first+cache.padding()); group->setValue("rectangle", glyph.second.padded(-cache.padding())); } std::ostringstream confOut; configuration.save(confOut); std::string confStr = confOut.str(); Containers::Array<char> confData{confStr.size()}; std::copy(confStr.begin(), confStr.end(), confData.begin()); /* Save cache image */ Image2D image(PixelFormat::Red, PixelType::UnsignedByte); cache.texture().image(0, image); auto tgaData = Trade::TgaImageConverter().exportToData(image); std::vector<std::pair<std::string, Containers::Array<char>>> out; out.emplace_back(filename + ".conf", std::move(confData)); out.emplace_back(filename + ".tga", std::move(tgaData)); return out; }
// TODO implementation of the server_draw_text function below is a small subset of possibilities text can be packed (detecting duplicated strings). See MS-RDPEGDI 2.2.2.2.1.1.2.13 GlyphIndex (GLYPHINDEX_ORDER) static inline void server_draw_text( GraphicApi & drawable, Font const & font, int16_t x, int16_t y, const char * text, uint32_t fgcolor, uint32_t bgcolor, const Rect & clip) { // TODO static not const is a bad idea static GlyphCache mod_glyph_cache; UTF8toUnicodeIterator unicode_iter(text); while (*unicode_iter) { int total_width = 0; int total_height = 0; uint8_t data[256]; auto data_begin = std::begin(data); const auto data_end = std::end(data)-2; const int cacheId = 7; int distance_from_previous_fragment = 0; while (data_begin != data_end) { const uint32_t charnum = *unicode_iter; if (!charnum) { break ; } ++unicode_iter; int cacheIndex = 0; FontChar const * font_item = font.glyph_at(charnum); if (!font_item) { LOG(LOG_WARNING, "server_draw_text() - character not defined >0x%02x<", charnum); font_item = &font.unknown_glyph(); } // TODO avoid passing parameters by reference to get results const GlyphCache::t_glyph_cache_result cache_result = mod_glyph_cache.add_glyph(*font_item, cacheId, cacheIndex); (void)cache_result; // supress warning *data_begin = cacheIndex; ++data_begin; *data_begin = distance_from_previous_fragment; ++data_begin; distance_from_previous_fragment = font_item->incby; total_width += font_item->incby; total_height = std::max(uint16_t(total_height), font_item->height); } const Rect bk(x, y, total_width + 1, total_height + 1); RDPGlyphIndex glyphindex( cacheId, // cache_id 0x03, // fl_accel 0x0, // ui_charinc 1, // f_op_redundant, fgcolor, // BackColor (text color) bgcolor, // ForeColor (color of the opaque rectangle) bk, // bk bk, // op // brush RDPBrush(0, 0, 3, 0xaa, reinterpret_cast<const uint8_t *>("\xaa\x55\xaa\x55\xaa\x55\xaa\x55")), x, // glyph_x y + total_height, // glyph_y data_begin - data, // data_len in bytes data // data ); x += total_width; drawable.draw(glyphindex, clip, mod_glyph_cache); } }