添加svg表情支持
This commit is contained in:
@@ -8,9 +8,17 @@ target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC mirage_core sokol)
|
||||
|
||||
option(MIRAGE_STB_IMAGE_LOADER "Use stb load image" ON)
|
||||
option(MIRAGE_SVG_EMOJI "Use svg emoji" ON)
|
||||
|
||||
if (MIRAGE_STB_IMAGE_LOADER)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC mirage_stb_image_loader)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC HAS_STB_IMAGE)
|
||||
endif ()
|
||||
if (MIRAGE_SVG_EMOJI)
|
||||
find_package(nanosvg REQUIRED)
|
||||
find_package(ZLIB REQUIRED)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC NanoSVG::nanosvg NanoSVG::nanosvgrast ZLIB::ZLIB)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC HAS_NANOSVG)
|
||||
endif ()
|
||||
|
||||
# 添加编译shader的自定义命令
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include "font_utils.h"
|
||||
#include "font_renderer/cbdt_renderer.h"
|
||||
#include "font_renderer/colr_renderer.h"
|
||||
#include "font_renderer/sbix_renderer.h"
|
||||
#include "font_renderer/standard_bitmap_renderer.h"
|
||||
#include "font_renderer/svg_renderer.h"
|
||||
|
||||
font_face_t::font_face_t() {
|
||||
// 延迟初始化渲染器,直到加载字体
|
||||
@@ -247,12 +250,14 @@ void font_face_t::init_renderers() {
|
||||
bitmap_renderer_ = std::make_unique<standard_bitmap_renderer_t>();
|
||||
|
||||
// 创建彩色表情渲染器
|
||||
#ifdef HAS_STB_IMAGE
|
||||
emoji_renderers_.push_back(std::make_unique<sbix_renderer_t>());
|
||||
#endif
|
||||
emoji_renderers_.push_back(std::make_unique<cbdt_renderer_t>());
|
||||
emoji_renderers_.push_back(std::make_unique<colr_renderer_t>());
|
||||
|
||||
// 未来可以在这里添加其他类型的彩色表情渲染器
|
||||
// emoji_renderers_.push_back(std::make_unique<cbdt_renderer_t>());
|
||||
// emoji_renderers_.push_back(std::make_unique<sbix_renderer_t>());
|
||||
// emoji_renderers_.push_back(std::make_unique<svg_renderer_t>());
|
||||
#ifdef HAS_NANOSVG
|
||||
emoji_renderers_.push_back(std::make_unique<svg_renderer_t>());
|
||||
#endif
|
||||
}
|
||||
|
||||
bool font_face_t::load_font_data(const std::wstring& font_path) {
|
||||
|
||||
295
src/mirage_render/font/font_renderer/cbdt_renderer.cpp
Normal file
295
src/mirage_render/font/font_renderer/cbdt_renderer.cpp
Normal file
@@ -0,0 +1,295 @@
|
||||
#include "cbdt_renderer.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
bool cbdt_renderer_t::supports_font(const uint8_t* font_data, int font_offset) const {
|
||||
// 检查CBDT和CBLC表是否同时存在
|
||||
return font::find_table_offset(font_data, font_offset, "CBDT") != 0 &&
|
||||
font::find_table_offset(font_data, font_offset, "CBLC") != 0;
|
||||
}
|
||||
|
||||
std::optional<color_emoji_bitmap_t> cbdt_renderer_t::render_emoji(
|
||||
const stbtt_fontinfo& font_info,
|
||||
const uint8_t* font_data,
|
||||
int font_offset,
|
||||
int32_t glyph_index,
|
||||
float font_size) const {
|
||||
|
||||
// 检查参数有效性
|
||||
if (glyph_index == 0 || font_size <= 0.0f) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 查找CBDT和CBLC表
|
||||
const uint32_t cbdt_offset = font::find_table_offset(font_data, font_offset, "CBDT");
|
||||
const uint32_t cblc_offset = font::find_table_offset(font_data, font_offset, "CBLC");
|
||||
|
||||
if (cbdt_offset == 0 || cblc_offset == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const uint8_t* cbdt_data = font_data + cbdt_offset;
|
||||
const uint8_t* cblc_data = font_data + cblc_offset;
|
||||
|
||||
// 查找字形位图
|
||||
auto bitmap_loc = find_bitmap(cblc_data, cbdt_data, glyph_index, font_size);
|
||||
if (!bitmap_loc) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 解码位图数据
|
||||
return decode_bitmap(*bitmap_loc);
|
||||
}
|
||||
|
||||
std::optional<cbdt_renderer_t::bitmap_location_t> cbdt_renderer_t::find_bitmap(
|
||||
const uint8_t* cblc_data,
|
||||
const uint8_t* cbdt_data,
|
||||
int32_t glyph_index,
|
||||
float target_size) const {
|
||||
|
||||
// 解析CBLC表头部
|
||||
// 2字节:majorVersion
|
||||
// 2字节:minorVersion
|
||||
// 4字节:numSizes(bitmapSize数量)
|
||||
const uint16_t major_version = font::read_u16(cblc_data);
|
||||
const uint16_t minor_version = font::read_u16(cblc_data + 2);
|
||||
const uint32_t num_sizes = font::read_u32(cblc_data + 4);
|
||||
|
||||
if (num_sizes == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 找到最佳匹配的bitmapSize(位图尺寸)
|
||||
uint32_t best_size_index = 0;
|
||||
uint16_t best_ppem = 0;
|
||||
uint16_t target_ppem = static_cast<uint16_t>(target_size);
|
||||
|
||||
// bitmapSize数组
|
||||
const uint8_t* bitmapSize_array = cblc_data + 8;
|
||||
|
||||
// 遍历所有bitmapSize,找到最接近目标尺寸的一个
|
||||
for (uint32_t i = 0; i < num_sizes; i++) {
|
||||
const uint8_t* size_table = bitmapSize_array + i * 48; // 每个bitmapSize表48字节
|
||||
|
||||
// 读取当前bitmapSize的ppem(像素每em)
|
||||
const uint16_t ppem_x = font::read_u16(size_table);
|
||||
const uint16_t ppem_y = font::read_u16(size_table + 2);
|
||||
const uint16_t ppem = std::max(ppem_x, ppem_y); // 通常两者相等
|
||||
|
||||
// 找到最接近的尺寸
|
||||
if (best_ppem == 0 ||
|
||||
(ppem >= target_ppem && ppem < best_ppem) ||
|
||||
(best_ppem < target_ppem && ppem > best_ppem)) {
|
||||
best_ppem = ppem;
|
||||
best_size_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取选中的bitmapSize数据
|
||||
const uint8_t* size_table = bitmapSize_array + best_size_index * 48;
|
||||
|
||||
// 读取indexSubTableArray偏移和数量
|
||||
const uint32_t index_subtable_array_offset = font::read_u32(size_table + 8);
|
||||
const uint32_t num_index_subtables = font::read_u32(size_table + 12);
|
||||
|
||||
// 访问indexSubTableArray
|
||||
const uint8_t* index_subtable_array = cblc_data + index_subtable_array_offset;
|
||||
|
||||
// 查找包含目标字形的索引子表
|
||||
for (uint32_t i = 0; i < num_index_subtables; i++) {
|
||||
const uint8_t* subtable_entry = index_subtable_array + i * 8;
|
||||
|
||||
// 读取firstGlyphIndex和lastGlyphIndex
|
||||
const uint16_t first_glyph = font::read_u16(subtable_entry);
|
||||
const uint16_t last_glyph = font::read_u16(subtable_entry + 2);
|
||||
|
||||
// 检查字形索引是否在范围内
|
||||
if (glyph_index >= first_glyph && glyph_index <= last_glyph) {
|
||||
// 找到了包含目标字形的子表
|
||||
const uint32_t subtable_offset = font::read_u32(subtable_entry + 4);
|
||||
const uint8_t* index_subtable = cblc_data + subtable_offset;
|
||||
|
||||
// 读取索引子表头部
|
||||
const uint16_t index_format = font::read_u16(index_subtable);
|
||||
const uint16_t image_format = font::read_u16(index_subtable + 2);
|
||||
const uint32_t image_data_offset = font::read_u32(index_subtable + 4);
|
||||
|
||||
// 根据索引格式查找位图数据
|
||||
uint32_t bitmap_offset = 0;
|
||||
uint32_t bitmap_length = 0;
|
||||
|
||||
// 处理不同的索引格式
|
||||
if (index_format == 1) {
|
||||
// 格式1:只有一个字形范围,所有字形共享相同的位图度量
|
||||
const uint8_t* sbit_line = index_subtable + 8;
|
||||
bitmap_offset = font::read_u32(sbit_line) + image_data_offset;
|
||||
bitmap_length = font::read_u32(sbit_line + 4);
|
||||
} else if (index_format == 2) {
|
||||
// 格式2:图像大小相同,但每个字形有不同的位图
|
||||
const uint16_t image_size = font::read_u32(index_subtable + 8);
|
||||
const uint32_t glyph_offset = (glyph_index - first_glyph) * image_size;
|
||||
bitmap_offset = image_data_offset + glyph_offset;
|
||||
bitmap_length = image_size;
|
||||
} else if (index_format == 3) {
|
||||
// 格式3:每个字形有偏移数组
|
||||
const uint16_t glyph_array_offset = 16 + (glyph_index - first_glyph) * 4;
|
||||
bitmap_offset = font::read_u32(index_subtable + glyph_array_offset) + image_data_offset;
|
||||
|
||||
// 下一个字形的偏移或表的结束位置确定长度
|
||||
uint32_t next_offset;
|
||||
if (glyph_index < last_glyph) {
|
||||
next_offset = font::read_u32(index_subtable + glyph_array_offset + 4) + image_data_offset;
|
||||
} else {
|
||||
// 估计最后一个字形的长度
|
||||
next_offset = bitmap_offset + 1024; // 假设最大长度
|
||||
}
|
||||
bitmap_length = next_offset - bitmap_offset;
|
||||
} else if (index_format == 4 || index_format == 5) {
|
||||
// 格式4和5:更复杂的索引方式,这里简化实现
|
||||
// 实际情况中需要根据具体字体完善
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果找到了位图偏移
|
||||
if (bitmap_offset > 0) {
|
||||
// 访问CBDT表中的位图数据
|
||||
const uint8_t* bitmap_data = cbdt_data + bitmap_offset;
|
||||
|
||||
// 创建位图位置信息
|
||||
bitmap_location_t location;
|
||||
|
||||
// 根据图像格式解析度量信息
|
||||
if (image_format == 17) {
|
||||
// 小字形位图格式 (SmallGlyphMetrics)
|
||||
const uint8_t height = font::read_u8(bitmap_data);
|
||||
const uint8_t width = font::read_u8(bitmap_data + 1);
|
||||
const int8_t bearing_x = font::read_i8(bitmap_data + 2);
|
||||
const int8_t bearing_y = font::read_i8(bitmap_data + 3);
|
||||
const uint8_t advance = font::read_u8(bitmap_data + 4);
|
||||
|
||||
location.data = bitmap_data + 5; // 跳过SmallGlyphMetrics
|
||||
location.data_format = image_format;
|
||||
location.size = bitmap_length - 5;
|
||||
location.width = width;
|
||||
location.height = height;
|
||||
location.bearing_x = bearing_x;
|
||||
location.bearing_y = bearing_y;
|
||||
location.bits_per_pixel = 32; // RGBA 8888
|
||||
|
||||
return location;
|
||||
}
|
||||
if (image_format == 18) {
|
||||
// 大字形位图格式 (BigGlyphMetrics)
|
||||
const uint8_t height = font::read_u8(bitmap_data);
|
||||
const uint8_t width = font::read_u8(bitmap_data + 1);
|
||||
const int8_t horiBearingX = font::read_i8(bitmap_data + 2);
|
||||
const int8_t horiBearingY = font::read_i8(bitmap_data + 3);
|
||||
const uint8_t horiAdvance = font::read_u8(bitmap_data + 4);
|
||||
const int8_t vertBearingX = font::read_i8(bitmap_data + 5);
|
||||
const int8_t vertBearingY = font::read_i8(bitmap_data + 6);
|
||||
const uint8_t vertAdvance = font::read_u8(bitmap_data + 7);
|
||||
|
||||
location.data = bitmap_data + 8; // 跳过BigGlyphMetrics
|
||||
location.data_format = image_format;
|
||||
location.size = bitmap_length - 8;
|
||||
location.width = width;
|
||||
location.height = height;
|
||||
location.bearing_x = horiBearingX;
|
||||
location.bearing_y = horiBearingY;
|
||||
location.bits_per_pixel = 32; // RGBA 8888
|
||||
|
||||
return location;
|
||||
}
|
||||
if (image_format == 19) {
|
||||
// 格式19:含调色板的位图
|
||||
const uint8_t height = font::read_u8(bitmap_data);
|
||||
const uint8_t width = font::read_u8(bitmap_data + 1);
|
||||
const int8_t bearing_x = font::read_i8(bitmap_data + 2);
|
||||
const int8_t bearing_y = font::read_i8(bitmap_data + 3);
|
||||
const uint8_t advance = font::read_u8(bitmap_data + 4);
|
||||
|
||||
location.data = bitmap_data; // 包含完整头部
|
||||
location.data_format = image_format;
|
||||
location.size = bitmap_length;
|
||||
location.width = width;
|
||||
location.height = height;
|
||||
location.bearing_x = bearing_x;
|
||||
location.bearing_y = bearing_y;
|
||||
location.bits_per_pixel = 8; // 调色板索引
|
||||
|
||||
return location;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 未找到匹配的位图
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<color_emoji_bitmap_t> cbdt_renderer_t::decode_bitmap(
|
||||
const bitmap_location_t& location) const {
|
||||
|
||||
if (!location.data || location.size == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 创建位图结构
|
||||
color_emoji_bitmap_t bitmap;
|
||||
bitmap.width = location.width;
|
||||
bitmap.height = location.height;
|
||||
bitmap.x_offset = location.bearing_x;
|
||||
bitmap.y_offset = location.bearing_y;
|
||||
bitmap.data.resize(location.width * location.height * 4, 0);
|
||||
|
||||
// 根据不同的格式解码位图
|
||||
if (location.data_format == 17 || location.data_format == 18) {
|
||||
// 格式17/18: 直接RGBA位图
|
||||
if (location.size >= location.width * location.height * 4) {
|
||||
std::memcpy(bitmap.data.data(), location.data, location.width * location.height * 4);
|
||||
} else {
|
||||
return std::nullopt; // 数据大小不足
|
||||
}
|
||||
} else if (location.data_format == 19) {
|
||||
// 格式19: 带调色板的位图
|
||||
const uint8_t* data = location.data;
|
||||
|
||||
// 跳过SmallGlyphMetrics (5字节)
|
||||
data += 5;
|
||||
|
||||
const uint8_t num_palette_entries = font::read_u8(data);
|
||||
const uint8_t color_ref_type = font::read_u8(data + 1);
|
||||
|
||||
// 调色板数据
|
||||
const uint8_t* palette = data + 2;
|
||||
|
||||
// 位图数据在调色板之后
|
||||
const uint8_t* image_data = palette + num_palette_entries * 4;
|
||||
|
||||
// 如果是调色板索引格式
|
||||
if (color_ref_type == 1) {
|
||||
for (uint16_t y = 0; y < location.height; y++) {
|
||||
for (uint16_t x = 0; x < location.width; x++) {
|
||||
const uint8_t index = image_data[y * location.width + x];
|
||||
if (index < num_palette_entries) {
|
||||
const uint8_t* color = palette + index * 4;
|
||||
const uint32_t pixel_offset = (y * location.width + x) * 4;
|
||||
|
||||
bitmap.data[pixel_offset] = color[0]; // R
|
||||
bitmap.data[pixel_offset + 1] = color[1]; // G
|
||||
bitmap.data[pixel_offset + 2] = color[2]; // B
|
||||
bitmap.data[pixel_offset + 3] = color[3]; // A
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 直接颜色格式 (不常见)
|
||||
return std::nullopt;
|
||||
}
|
||||
} else {
|
||||
// 不支持的格式
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
73
src/mirage_render/font/font_renderer/cbdt_renderer.h
Normal file
73
src/mirage_render/font/font_renderer/cbdt_renderer.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
#include "font_renderer.h"
|
||||
#include "font/font_utils.h"
|
||||
|
||||
/**
|
||||
* @class cbdt_renderer_t
|
||||
* @brief 基于CBDT/CBLC表的彩色表情渲染器
|
||||
*
|
||||
* 实现了Google CBDT/CBLC彩色字体格式的渲染,主要用于Android彩色表情
|
||||
* CBDT表存储位图数据,CBLC表存储位图位置信息
|
||||
*/
|
||||
class cbdt_renderer_t : public color_emoji_renderer_t {
|
||||
public:
|
||||
/**
|
||||
* @brief 检查字体是否支持CBDT/CBLC表
|
||||
* @param font_data 字体数据
|
||||
* @param font_offset 字体偏移
|
||||
* @return 是否支持
|
||||
*/
|
||||
bool supports_font(const uint8_t* font_data, int font_offset) const override;
|
||||
|
||||
/**
|
||||
* @brief 渲染CBDT/CBLC彩色表情
|
||||
* @param font_info 字体信息
|
||||
* @param font_data 字体数据
|
||||
* @param font_offset 字体偏移
|
||||
* @param glyph_index 字形索引
|
||||
* @param font_size 字体大小
|
||||
* @return 彩色位图数据
|
||||
*/
|
||||
std::optional<color_emoji_bitmap_t> render_emoji(
|
||||
const stbtt_fontinfo& font_info,
|
||||
const uint8_t* font_data,
|
||||
int font_offset,
|
||||
int32_t glyph_index,
|
||||
float font_size) const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 存储位图数据的位置和信息
|
||||
*/
|
||||
struct bitmap_location_t {
|
||||
const uint8_t* data = nullptr; // 位图数据起点
|
||||
uint32_t data_format = 0; // 数据格式
|
||||
uint32_t size = 0; // 数据大小
|
||||
uint16_t width = 0; // 位图宽度
|
||||
uint16_t height = 0; // 位图高度
|
||||
int16_t bearing_x = 0; // 水平原点偏移
|
||||
int16_t bearing_y = 0; // 垂直原点偏移
|
||||
uint8_t bits_per_pixel = 0; // 像素位深
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 在CBLC/CBDT表中查找字形位图
|
||||
* @param cblc_data CBLC表数据
|
||||
* @param cbdt_data CBDT表数据
|
||||
* @param glyph_index 字形索引
|
||||
* @param target_size 目标尺寸
|
||||
* @return 位图位置信息
|
||||
*/
|
||||
std::optional<bitmap_location_t> find_bitmap(
|
||||
const uint8_t* cblc_data,
|
||||
const uint8_t* cbdt_data,
|
||||
int32_t glyph_index,
|
||||
float target_size) const;
|
||||
|
||||
/**
|
||||
* @brief 解码CBDT格式位图
|
||||
* @param location 位图位置信息
|
||||
* @return 解码后的RGBA位图
|
||||
*/
|
||||
std::optional<color_emoji_bitmap_t> decode_bitmap(const bitmap_location_t& location) const;
|
||||
};
|
||||
220
src/mirage_render/font/font_renderer/sbix_renderer.cpp
Normal file
220
src/mirage_render/font/font_renderer/sbix_renderer.cpp
Normal file
@@ -0,0 +1,220 @@
|
||||
#include "sbix_renderer.h"
|
||||
#ifdef HAS_STB_IMAGE
|
||||
#include <cstring>
|
||||
|
||||
#include "stb_image.h"
|
||||
#include "font/font_utils.h"
|
||||
|
||||
bool sbix_renderer_t::supports_font(const uint8_t* font_data, int font_offset) const {
|
||||
// 检查是否存在sbix表
|
||||
return font::find_table_offset(font_data, font_offset, "sbix") != 0;
|
||||
}
|
||||
|
||||
std::optional<color_emoji_bitmap_t> sbix_renderer_t::render_emoji(
|
||||
const stbtt_fontinfo& font_info,
|
||||
const uint8_t* font_data,
|
||||
int font_offset,
|
||||
int32_t glyph_index,
|
||||
float font_size) const {
|
||||
|
||||
// 检查参数有效性
|
||||
if (glyph_index == 0 || font_size <= 0.0f) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 查找sbix表
|
||||
const uint32_t sbix_offset = font::find_table_offset(font_data, font_offset, "sbix");
|
||||
if (sbix_offset == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const uint8_t* sbix_data = font_data + sbix_offset;
|
||||
|
||||
// 查找最佳尺寸的位图
|
||||
auto bitmap_loc = find_best_bitmap(font_info, sbix_data, glyph_index, font_size);
|
||||
if (!bitmap_loc) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 目前只支持PNG格式
|
||||
// sbix表中的graphic_type是4字节标记,通常为'png '
|
||||
if (bitmap_loc->graphic_type != 0x706E6720) { // 'png '的大端字节值
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 解码PNG格式的位图
|
||||
auto bitmap = decode_png(bitmap_loc->data, bitmap_loc->size);
|
||||
if (!bitmap) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 记录位图的原点偏移(可用于定位)
|
||||
bitmap->x_offset = bitmap_loc->x_offset;
|
||||
bitmap->y_offset = bitmap_loc->y_offset;
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
std::optional<sbix_renderer_t::bitmap_location_t> sbix_renderer_t::find_best_bitmap(
|
||||
const stbtt_fontinfo& font_info,
|
||||
const uint8_t* sbix_data,
|
||||
int32_t glyph_index,
|
||||
float target_size) const {
|
||||
|
||||
// 解析sbix表头部
|
||||
// 2字节:version
|
||||
// 2字节:flags
|
||||
// 4字节:numStrikes(trike数量,每个strike对应一个像素尺寸)
|
||||
// 变长:strike偏移数组
|
||||
|
||||
const uint16_t version = font::read_u16(sbix_data);
|
||||
const uint16_t flags = font::read_u16(sbix_data + 2);
|
||||
const uint32_t num_strikes = font::read_u32(sbix_data + 4);
|
||||
|
||||
if (num_strikes == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 找到最佳匹配的strike(位图尺寸)
|
||||
// 每一个strike包含一组相同尺寸的位图
|
||||
uint32_t best_strike_index = 0;
|
||||
uint16_t best_ppem = 0;
|
||||
uint16_t target_ppem = static_cast<uint16_t>(target_size);
|
||||
|
||||
// strike偏移数组
|
||||
const uint8_t* strike_offset_array = sbix_data + 8;
|
||||
|
||||
// 遍历所有strike,找到最接近目标尺寸的strike
|
||||
for (uint32_t i = 0; i < num_strikes; i++) {
|
||||
const uint32_t strike_offset = font::read_u32(strike_offset_array + i * 4);
|
||||
const uint8_t* strike_data = sbix_data + strike_offset;
|
||||
|
||||
// 读取当前strike的ppem(像素每em)
|
||||
const uint16_t ppem = font::read_u16(strike_data);
|
||||
|
||||
// 找到最接近的尺寸
|
||||
if (best_ppem == 0 ||
|
||||
(ppem >= target_ppem && ppem < best_ppem) ||
|
||||
(best_ppem < target_ppem && ppem > best_ppem)) {
|
||||
best_ppem = ppem;
|
||||
best_strike_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取选中的strike数据
|
||||
const uint32_t strike_offset = font::read_u32(strike_offset_array + best_strike_index * 4);
|
||||
const uint8_t* strike_data = sbix_data + strike_offset;
|
||||
|
||||
// 读取strike头部
|
||||
// 2字节:ppem
|
||||
// 2字节:ppi(每英寸像素数)
|
||||
// 变长:glyphOffset数组(每个字形对应一个偏移)
|
||||
|
||||
// 获取当前字体的最大字形ID
|
||||
int num_glyphs = 0;
|
||||
stbtt_GetFontBoundingBox(&font_info, nullptr, nullptr, nullptr, nullptr); // 确保已初始化
|
||||
if (font_info.numGlyphs > 0) {
|
||||
num_glyphs = font_info.numGlyphs;
|
||||
} else {
|
||||
// 如果stbtt_fontinfo中没有,尝试从maxp表获取
|
||||
const uint32_t maxp_offset = font::find_table_offset(
|
||||
reinterpret_cast<const uint8_t*>(font_info.data),
|
||||
0, "maxp");
|
||||
if (maxp_offset) {
|
||||
num_glyphs = font::read_u16(reinterpret_cast<const uint8_t*>(font_info.data) + maxp_offset + 4);
|
||||
}
|
||||
|
||||
if (num_glyphs <= 0) {
|
||||
return std::nullopt; // 无法确定字形数量
|
||||
}
|
||||
}
|
||||
|
||||
// 检查字形索引是否有效
|
||||
if (glyph_index >= num_glyphs) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 字形偏移数组
|
||||
const uint8_t* glyph_offset_array = strike_data + 4;
|
||||
|
||||
// 获取当前字形的数据偏移和下一个字形的数据偏移
|
||||
const uint32_t glyph_offset = font::read_u32(glyph_offset_array + glyph_index * 4);
|
||||
uint32_t next_glyph_offset;
|
||||
|
||||
if (glyph_index + 1 < num_glyphs) {
|
||||
next_glyph_offset = font::read_u32(glyph_offset_array + (glyph_index + 1) * 4);
|
||||
} else {
|
||||
// 如果是最后一个字形,使用strike的总大小作为边界
|
||||
const uint32_t next_strike_offset = (best_strike_index + 1 < num_strikes) ?
|
||||
font::read_u32(strike_offset_array + (best_strike_index + 1) * 4) :
|
||||
font::find_table_offset(reinterpret_cast<const uint8_t*>(font_info.data), 0, "sbix") + font::read_u32(reinterpret_cast<const uint8_t*>(font_info.data) + font::find_table_offset(reinterpret_cast<const uint8_t*>(font_info.data), 0, "sbix") + 8);
|
||||
|
||||
next_glyph_offset = next_strike_offset - strike_offset;
|
||||
}
|
||||
|
||||
// 如果当前字形没有位图数据或偏移无效
|
||||
if (glyph_offset >= next_glyph_offset || glyph_offset == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 访问字形数据
|
||||
const uint8_t* glyph_data = strike_data + glyph_offset;
|
||||
|
||||
// sbix字形记录格式:
|
||||
// 2字节:originOffsetX(水平原点偏移)
|
||||
// 2字节:originOffsetY(垂直原点偏移)
|
||||
// 4字节:graphicType(图像格式,如'png ')
|
||||
// 变长:图像数据
|
||||
|
||||
const int16_t x_offset = font::read_i16(glyph_data);
|
||||
const int16_t y_offset = font::read_i16(glyph_data + 2);
|
||||
const uint32_t graphic_type = font::read_u32(glyph_data + 4);
|
||||
|
||||
// 计算位图数据大小
|
||||
const uint32_t bitmap_size = next_glyph_offset - glyph_offset - 8;
|
||||
|
||||
// 返回位图位置信息
|
||||
bitmap_location_t location;
|
||||
location.data = glyph_data + 8; // 跳过头部
|
||||
location.size = bitmap_size;
|
||||
location.graphic_type = graphic_type;
|
||||
location.x_offset = x_offset;
|
||||
location.y_offset = y_offset;
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
std::optional<color_emoji_bitmap_t> sbix_renderer_t::decode_png(
|
||||
const uint8_t* data,
|
||||
size_t data_size) const {
|
||||
|
||||
if (!data || data_size == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 使用stb_image解码PNG数据
|
||||
int width, height, channels;
|
||||
unsigned char* decoded = stbi_load_from_memory(
|
||||
data, static_cast<int>(data_size),
|
||||
&width, &height, &channels, 4); // 强制转换为RGBA
|
||||
|
||||
if (!decoded) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 创建并填充位图结构
|
||||
color_emoji_bitmap_t bitmap;
|
||||
bitmap.width = width;
|
||||
bitmap.height = height;
|
||||
bitmap.data.resize(width * height * 4);
|
||||
|
||||
// 复制解码后的数据
|
||||
std::memcpy(bitmap.data.data(), decoded, width * height * 4);
|
||||
|
||||
// 释放stb_image分配的内存
|
||||
stbi_image_free(decoded);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
#endif
|
||||
75
src/mirage_render/font/font_renderer/sbix_renderer.h
Normal file
75
src/mirage_render/font/font_renderer/sbix_renderer.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
#include "font_renderer.h"
|
||||
|
||||
#ifdef HAS_STB_IMAGE
|
||||
|
||||
/**
|
||||
* @class sbix_renderer_t
|
||||
* @brief 基于sbix表的彩色表情渲染器
|
||||
*
|
||||
* 实现了Apple sbix彩色字体格式的渲染,主要用于Apple Color Emoji字体
|
||||
* sbix表存储预渲染的位图表情,通常是PNG格式图像
|
||||
*/
|
||||
class sbix_renderer_t : public color_emoji_renderer_t {
|
||||
public:
|
||||
/**
|
||||
* @brief 检查字体是否支持sbix表
|
||||
* @param font_data 字体数据
|
||||
* @param font_offset 字体偏移
|
||||
* @return 是否支持
|
||||
*/
|
||||
bool supports_font(const uint8_t* font_data, int font_offset) const override;
|
||||
|
||||
/**
|
||||
* @brief 渲染sbix彩色表情
|
||||
* @param font_info 字体信息
|
||||
* @param font_data 字体数据
|
||||
* @param font_offset 字体偏移
|
||||
* @param glyph_index 字形索引
|
||||
* @param font_size 字体大小
|
||||
* @return 彩色位图数据
|
||||
*/
|
||||
std::optional<color_emoji_bitmap_t> render_emoji(
|
||||
const stbtt_fontinfo& font_info,
|
||||
const uint8_t* font_data,
|
||||
int font_offset,
|
||||
int32_t glyph_index,
|
||||
float font_size) const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 存储位图数据的位置和信息
|
||||
*/
|
||||
struct bitmap_location_t {
|
||||
const uint8_t* data = nullptr; // 位图数据起点
|
||||
uint32_t size = 0; // 数据大小
|
||||
uint32_t graphic_type = 0; // 数据格式 (如 'png ')
|
||||
int16_t x_offset = 0; // 水平原点偏移
|
||||
int16_t y_offset = 0; // 垂直原点偏移
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 在sbix表中查找最适合的位图
|
||||
* @param sbix_data sbix表数据
|
||||
* @param glyph_index 字形索引
|
||||
* @param target_size 目标尺寸
|
||||
* @return 位图位置信息
|
||||
*/
|
||||
std::optional<bitmap_location_t> find_best_bitmap(
|
||||
const stbtt_fontinfo& font_info,
|
||||
const uint8_t* sbix_data,
|
||||
int32_t glyph_index,
|
||||
float target_size) const;
|
||||
|
||||
/**
|
||||
* @brief 解码PNG格式位图
|
||||
* @param data PNG数据
|
||||
* @param data_size 数据大小
|
||||
* @return 解码后的RGBA位图
|
||||
*/
|
||||
std::optional<color_emoji_bitmap_t> decode_png(
|
||||
const uint8_t* data,
|
||||
size_t data_size) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
169
src/mirage_render/font/font_renderer/svg_renderer.cpp
Normal file
169
src/mirage_render/font/font_renderer/svg_renderer.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
#include "svg_renderer.h"
|
||||
|
||||
#ifdef HAS_NANOSVG
|
||||
|
||||
#include <cstring>
|
||||
#include <zlib.h> // 用于解压缩SVGZ
|
||||
|
||||
// 使用NanoSVG作为SVG解析和渲染器
|
||||
#define NANOSVG_IMPLEMENTATION
|
||||
#include "nanosvg.h"
|
||||
#define NANOSVGRAST_IMPLEMENTATION
|
||||
#include "nanosvgrast.h"
|
||||
|
||||
svg_renderer_t::svg_renderer_t() {
|
||||
// 初始化任何需要的资源
|
||||
}
|
||||
|
||||
svg_renderer_t::~svg_renderer_t() {
|
||||
// 清理资源
|
||||
}
|
||||
|
||||
bool svg_renderer_t::supports_font(const uint8_t* font_data, int font_offset) const {
|
||||
// 检查字体是否包含SVG表
|
||||
return font::find_table_offset(font_data, font_offset, "SVG ") != 0;
|
||||
}
|
||||
|
||||
std::optional<color_emoji_bitmap_t> svg_renderer_t::render_emoji(
|
||||
const stbtt_fontinfo& font_info,
|
||||
const uint8_t* font_data,
|
||||
int font_offset,
|
||||
int32_t glyph_index,
|
||||
float font_size) const {
|
||||
|
||||
// 验证参数
|
||||
if (glyph_index == 0 || font_size <= 0.0f) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 使用stb_truetype提供的API获取SVG数据
|
||||
const char* svg_data = nullptr;
|
||||
int result = stbtt_GetGlyphSVG(&font_info, glyph_index, &svg_data);
|
||||
|
||||
if (result == 0 || svg_data == nullptr) {
|
||||
return std::nullopt; // 没有找到SVG数据
|
||||
}
|
||||
|
||||
// 计算渲染尺寸
|
||||
const float scale = stbtt_ScaleForPixelHeight(&font_info, font_size);
|
||||
int x0, y0, x1, y1;
|
||||
stbtt_GetGlyphBitmapBox(&font_info, glyph_index, scale, scale, &x0, &y0, &x1, &y1);
|
||||
|
||||
int width = x1 - x0;
|
||||
int height = y1 - y0;
|
||||
|
||||
// 确保尺寸有效
|
||||
if (width <= 0 || height <= 0) {
|
||||
width = static_cast<int>(font_size);
|
||||
height = static_cast<int>(font_size);
|
||||
}
|
||||
|
||||
// 渲染SVG为位图
|
||||
return render_svg_to_bitmap(svg_data, width, height);
|
||||
}
|
||||
|
||||
std::optional<color_emoji_bitmap_t> svg_renderer_t::render_svg_to_bitmap(
|
||||
const char* svg_data,
|
||||
int width,
|
||||
int height) const {
|
||||
|
||||
if (!svg_data || width <= 0 || height <= 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 检查是否是SVGZ (gzip压缩的SVG)
|
||||
const uint8_t* data_bytes = reinterpret_cast<const uint8_t*>(svg_data);
|
||||
bool is_compressed = data_bytes[0] == 0x1F && data_bytes[1] == 0x8B;
|
||||
|
||||
// 准备SVG字符串
|
||||
std::string svg_str;
|
||||
|
||||
if (is_compressed) {
|
||||
// 获取压缩数据的长度(近似值,因为stbtt_GetGlyphSVG不返回长度)
|
||||
size_t compressed_size = 0;
|
||||
while (data_bytes[compressed_size]) {
|
||||
compressed_size++;
|
||||
// 安全检查,防止无限循环
|
||||
if (compressed_size > 10 * 1024 * 1024) { // 10MB限制
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// 解压缩SVGZ
|
||||
z_stream zs = {0};
|
||||
if (inflateInit2(&zs, 15 + 32) != Z_OK) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 设置输入
|
||||
zs.avail_in = static_cast<uInt>(compressed_size);
|
||||
zs.next_in = const_cast<uint8_t*>(data_bytes);
|
||||
|
||||
// 准备输出缓冲区
|
||||
const size_t buffer_size = 16384;
|
||||
char buffer[buffer_size];
|
||||
|
||||
// 解压缩循环
|
||||
int ret;
|
||||
do {
|
||||
zs.avail_out = buffer_size;
|
||||
zs.next_out = reinterpret_cast<uint8_t*>(buffer);
|
||||
|
||||
ret = inflate(&zs, Z_NO_FLUSH);
|
||||
if (ret == Z_STREAM_ERROR) {
|
||||
inflateEnd(&zs);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
svg_str.append(buffer, buffer_size - zs.avail_out);
|
||||
} while (zs.avail_out == 0);
|
||||
|
||||
inflateEnd(&zs);
|
||||
} else {
|
||||
// 直接使用未压缩的SVG数据
|
||||
svg_str = svg_data;
|
||||
}
|
||||
|
||||
// 使用NanoSVG解析SVG
|
||||
NSVGimage* svg_image = nsvgParse(const_cast<char*>(svg_str.c_str()), "px", 96.0f);
|
||||
if (!svg_image) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 创建栅格化器
|
||||
NSVGrasterizer* rast = nsvgCreateRasterizer();
|
||||
if (!rast) {
|
||||
nsvgDelete(svg_image);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 创建位图结构
|
||||
color_emoji_bitmap_t bitmap;
|
||||
bitmap.width = width;
|
||||
bitmap.height = height;
|
||||
bitmap.data.resize(width * height * 4, 0);
|
||||
|
||||
// 栅格化SVG到位图
|
||||
float scale_factor = std::min(
|
||||
static_cast<float>(width) / svg_image->width,
|
||||
static_cast<float>(height) / svg_image->height
|
||||
);
|
||||
|
||||
nsvgRasterize(
|
||||
rast,
|
||||
svg_image,
|
||||
0, 0,
|
||||
scale_factor,
|
||||
bitmap.data.data(),
|
||||
width, height,
|
||||
width * 4
|
||||
);
|
||||
|
||||
// 清理资源
|
||||
nsvgDeleteRasterizer(rast);
|
||||
nsvgDelete(svg_image);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
#endif
|
||||
65
src/mirage_render/font/font_renderer/svg_renderer.h
Normal file
65
src/mirage_render/font/font_renderer/svg_renderer.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
#include "font_renderer.h"
|
||||
#include "font/font_utils.h"
|
||||
#include <string>
|
||||
|
||||
#ifdef HAS_NANOSVG
|
||||
|
||||
/**
|
||||
* @class svg_renderer_t
|
||||
* @brief 基于SVG表的彩色表情渲染器,使用stb_truetype提供的SVG接口
|
||||
*
|
||||
* 实现了OpenType SVG彩色字体格式的渲染
|
||||
* SVG表存储矢量图形表情,支持无损缩放
|
||||
*/
|
||||
class svg_renderer_t : public color_emoji_renderer_t {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
svg_renderer_t();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~svg_renderer_t();
|
||||
|
||||
/**
|
||||
* @brief 检查字体是否支持SVG表
|
||||
* @param font_data 字体数据
|
||||
* @param font_offset 字体偏移
|
||||
* @return 是否支持
|
||||
*/
|
||||
bool supports_font(const uint8_t* font_data, int font_offset) const override;
|
||||
|
||||
/**
|
||||
* @brief 渲染SVG彩色表情
|
||||
* @param font_info 字体信息
|
||||
* @param font_data 字体数据
|
||||
* @param font_offset 字体偏移
|
||||
* @param glyph_index 字形索引
|
||||
* @param font_size 字体大小
|
||||
* @return 彩色位图数据
|
||||
*/
|
||||
std::optional<color_emoji_bitmap_t> render_emoji(
|
||||
const stbtt_fontinfo& font_info,
|
||||
const uint8_t* font_data,
|
||||
int font_offset,
|
||||
int32_t glyph_index,
|
||||
float font_size) const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 渲染SVG为位图
|
||||
* @param svg_data SVG数据
|
||||
* @param width 目标宽度
|
||||
* @param height 目标高度
|
||||
* @return 渲染后的位图
|
||||
*/
|
||||
std::optional<color_emoji_bitmap_t> render_svg_to_bitmap(
|
||||
const char* svg_data,
|
||||
int width,
|
||||
int height) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -154,7 +154,9 @@ struct text_layout_t {
|
||||
* @brief 彩色表情位图
|
||||
*/
|
||||
struct color_emoji_bitmap_t {
|
||||
std::vector<uint8_t> data; // RGBA像素数据
|
||||
int width = 0; // 宽度
|
||||
int height = 0; // 高度
|
||||
int width = 0; // 位图宽度
|
||||
int height = 0; // 位图高度
|
||||
int16_t x_offset = 0; // 水平原点偏移
|
||||
int16_t y_offset = 0; // 垂直原点偏移
|
||||
std::vector<uint8_t> data; // RGBA数据,每个像素4字节
|
||||
};
|
||||
|
||||
@@ -2,6 +2,20 @@
|
||||
#include <cstdint>
|
||||
|
||||
namespace font {
|
||||
/**
|
||||
* @brief 读取无符号8位整数
|
||||
* @param p 数据指针
|
||||
* @return 8位无符号整数
|
||||
*/
|
||||
static uint8_t read_u8(const uint8_t* p) { return p[0]; }
|
||||
|
||||
/**
|
||||
* @brief 读取有符号8位整数
|
||||
* @param p 数据指针
|
||||
* @return 8位有符号整数
|
||||
*/
|
||||
static int8_t read_i8(const uint8_t* p) { return static_cast<int8_t>(p[0]); }
|
||||
|
||||
/**
|
||||
* @brief 读取无符号短整型(2字节,大端序)
|
||||
* @param p 数据指针
|
||||
@@ -9,6 +23,13 @@ namespace font {
|
||||
*/
|
||||
static uint16_t read_u16(const uint8_t* p) { return static_cast<uint16_t>(p[0] << 8 | p[1]); }
|
||||
|
||||
/**
|
||||
* @brief 读取有符号短整型(2字节,大端序)
|
||||
* @param p 数据指针
|
||||
* @return 16位有符号整数
|
||||
*/
|
||||
static int16_t read_i16(const uint8_t* p) { return static_cast<int16_t>(p[0] << 8 | p[1]); }
|
||||
|
||||
/**
|
||||
* @brief 读取无符号整型(4字节,大端序)
|
||||
* @param p 数据指针
|
||||
|
||||
Reference in New Issue
Block a user