#include "text.h" #include "include/core/SkBitmap.h" #include "include/core/SkCanvas.h" #include "include/core/SkFont.h" #include "include/core/SkFontMetrics.h" #include "include/core/SkFontMgr.h" #include "include/core/SkPaint.h" #include "include/core/SkTypeface.h" #include "include/ports/SkFontMgr_empty.h" #include #include #include #include namespace raven { static uint32_t field(std::span bytes,size_t at,size_t count=4) { uint32_t value=0; for (size_t i=0;i,3> faces; // Every bundled style: family (sans, mono, serif) by regular, bold, italic, bold italic. std::array,4>,3> styled; static bool contains_any(const std::string &name,std::initializer_list words) { for (const auto *word : words) if (name.find(word)!=name.npos) return true; return false; } // Maps any logical font onto the bundled families and style, sizing it the way GDI does. SkFont logical(std::span definition) { const auto height=int32_t(field(definition,0)),weight=int32_t(field(definition,16)); const bool italic=definition[20]!=0; const uint8_t quality=definition[26],pitch=definition[27]; std::string name; for (size_t i=0;i<32;++i) { const auto c=field(definition,28+i*2,2); if (!c) break; name.push_back(c<128 ? char(c>='A' && c<='Z' ? c+32 : c) : '?'); } size_t family=0; if (contains_any(name,{"courier","consolas","mono","fixed","lucida console","terminal","menlo","monaco","typewriter"}) || ((pitch&0x30)==0x30 && !contains_any(name,{"sans","arial","tahoma","segoe"}))) family=1; else if (contains_any(name,{"times","serif","georgia","cambria","garamond","palatino","book antiqua","century"}) && !contains_any(name,{"sans"})) family=2; const size_t style=(weight>=600 ? 1U : 0U)|(italic ? 2U : 0U); auto face=styled[family][style]; if (!face) face=styled[family][0]; if (!face) face=faces[family]; float size=height<0 ? float(-height) : height>0 ? float(height) : 12.0f; SkFont probe(face,100.0f); SkFontMetrics source; probe.getMetrics(&source); if (height>0) { // A positive height is the whole cell (ascent plus descent); scale so the rounded cell matches it. const float cell=(-source.fAscent+source.fDescent)/100.0f; size=float(height)/cell; SkFont trial(face,size); SkFontMetrics measured; trial.getMetrics(&measured); const float adjusted=float(height)/std::max(1.0f,float(std::lround(-measured.fAscent)+std::lround(measured.fDescent)))*size; if (std::fabs(adjusted-size) font(std::span definition) { const auto height=int32_t(field(definition,0)); if (height>=0 || height < -256 || field(definition,4) || field(definition,8) || field(definition,12) || (field(definition,16)!=400 && field(definition,16)!=0) || definition[20] || definition[21] || definition[22] || definition[23] || definition[24] || definition[25] || definition[26]!=3 || definition[27]) throw uint32_t(120); std::string name; for (size_t i=0;i<32;++i) { const auto c=field(definition,28+i*2,2); if (!c) break; if (c>127) throw uint32_t(120); name.push_back(char(c>='A' && c<='Z' ? c+32 : c)); } size_t family; if (name=="liberation sans" || name=="arial") family=0; else if (name=="liberation mono" || name=="courier new") family=1; else if (name=="liberation serif" || name=="times new roman") family=2; else throw uint32_t(120); SkFont result(faces[family],SkScalar(-height)); result.setHinting(SkFontHinting::kFull); result.setSubpixel(false); result.setLinearMetrics(false); result.setEdging(SkFont::Edging::kAntiAlias); return {result,family}; } }; TextRenderer::TextRenderer(const std::string &directory) : state(std::make_unique()) { auto manager=SkFontMgr_New_Custom_Empty(); const std::array files{"LiberationSans-Regular.ttf","LiberationMono-Regular.ttf","LiberationSerif-Regular.ttf"}; for (size_t i=0;ifaces[i]=manager->makeFromFile((directory+"/"+files[i]).c_str()); if (!state->faces[i]) throw std::runtime_error("cannot load qualified native font file"); state->styled[i][0]=state->faces[i]; } const std::array,3> styles{{ {"LiberationSans-Bold.ttf","LiberationSans-Italic.ttf","LiberationSans-BoldItalic.ttf"}, {"LiberationMono-Bold.ttf","LiberationMono-Italic.ttf","LiberationMono-BoldItalic.ttf"}, {"LiberationSerif-Bold.ttf","LiberationSerif-Italic.ttf","LiberationSerif-BoldItalic.ttf"}}}; for (size_t family=0;family<3;++family) for (size_t style=0;style<3;++style) state->styled[family][style+1]=manager->makeFromFile((directory+"/"+styles[family][style]).c_str()); } TextRenderer::~TextRenderer() = default; TextMetrics TextRenderer::metrics(std::span definition) { auto [font,family]=state->font(definition); SkFontMetrics source; font.getMetrics(&source); const auto rounded=[](float value) { return int32_t(std::lround(value)); }; const auto ascent=rounded(-source.fAscent),descent=rounded(source.fDescent); std::array table{}; if (state->faces[family]->getTableData(0x4f532f32,0,table.size(),table.data())!=table.size()) throw std::runtime_error("qualified font has no complete OS/2 metrics"); const auto character=[&](size_t at) { return uint16_t((uint16_t(table[at])<<8)|table[at+1]); }; return {ascent+descent,ascent,descent,rounded(-source.fAscent+source.fDescent-font.getSize()),rounded(source.fLeading), rounded(source.fAvgCharWidth),rounded(source.fMaxCharWidth),character(64),character(66),31,character(92), uint8_t(family==0 ? 39 : family==1 ? 54 : 23)}; } static std::vector glyphs(const SkFont &font,std::span text) { if (text.size()>4096) throw uint32_t(87); for (const auto c : text) if (!((c>=32 && c<=126) || (c>=0x00a0 && c<=0x024f) || (c>=0x0370 && c<=0x03ff) || (c>=0x1e00 && c<=0x1eff))) throw uint32_t(120); std::vector result(text.size()); const auto count=font.textToGlyphs(text.data(),text.size_bytes(),SkTextEncoding::kUTF16,result); if (count!=text.size()) throw uint32_t(120); for (const auto glyph : result) if (!glyph) throw uint32_t(120); return result; } TextExtent TextRenderer::extent(std::span definition,std::span text) { auto [font,family]=state->font(definition); (void)family; const auto ids=glyphs(font,text); std::vector widths(ids.size()); font.getWidths(ids,widths); float width=0; for (const auto value : widths) width+=value; return {int32_t(std::lround(width)),metrics(definition).height}; } std::vector TextRenderer::mask(std::span definition,std::span text, uint32_t width,uint32_t height,int32_t x,int32_t y,std::span> positions) { if (!width || !height || width>8192 || height>8192 || uint64_t(width)*height>16*1024*1024) throw uint32_t(87); auto [font,family]=state->font(definition); (void)family; const auto ids=glyphs(font,text); font.setEdging(SkFont::Edging::kAlias); std::vector pixels(size_t(width)*height*4); SkBitmap bitmap; if (!bitmap.installPixels(SkImageInfo::Make(int(width),int(height),kBGRA_8888_SkColorType,kOpaque_SkAlphaType),pixels.data(),size_t(width)*4)) throw std::bad_alloc(); SkCanvas canvas(bitmap); canvas.clear(SK_ColorWHITE); SkPaint paint; paint.setColor(SK_ColorBLACK); const auto ascent=metrics(definition).ascent; if(positions.empty())canvas.drawSimpleText(text.data(),text.size_bytes(),SkTextEncoding::kUTF16,SkScalar(x),SkScalar(int64_t(y)+ascent),font,paint); else{ if(positions.size()!=ids.size())throw uint32_t(87); std::vector points;points.reserve(positions.size()); for(const auto &position:positions)points.push_back({SkScalar(int64_t(x)+position[0]),SkScalar(int64_t(y)+ascent+position[1])}); canvas.drawGlyphs({ids.data(),ids.size()},{points.data(),points.size()},{0,0},font,paint); } std::vector coverage(size_t(width)*height); for (size_t i=0;i TextRenderer::advances(std::span definition,std::span text) { auto [font,family]=state->font(definition);(void)family; const auto ids=glyphs(font,text);std::vector widths(ids.size());font.getWidths(ids,widths); std::vector result;result.reserve(widths.size());float total=0; for(const auto width:widths){total+=width;result.push_back(int32_t(std::lround(total)));} return result; } // The glyphs for UTF-16 text; characters the face lacks use its replacement glyph and controls draw nothing. static std::vector logical_glyphs(const SkFont &font,std::span text) { std::vector result(text.size(),0); std::vector printable; for (size_t i=0;i definition) { // Fonts inside the qualified subset keep their measured OS/2 metrics. try { return metrics(definition); } catch (const uint32_t &) {} auto font=state->logical(definition); SkFontMetrics source; font.getMetrics(&source); const auto rounded=[](float value) { return int32_t(std::lround(value)); }; const auto ascent=rounded(-source.fAscent),descent=rounded(source.fDescent); const auto average=source.fAvgCharWidth>0 ? source.fAvgCharWidth : font.getSize()*0.5f; const auto maximum=source.fMaxCharWidth>0 ? source.fMaxCharWidth : font.getSize(); // Fixed-pitch faces report no variable-pitch flag. const auto *face=font.getTypeface(); const bool fixed=face==state->styled[1][0].get() || face==state->styled[1][1].get() || face==state->styled[1][2].get() || face==state->styled[1][3].get(); return {ascent+descent,ascent,descent,std::max(0,rounded(-source.fAscent+source.fDescent-font.getSize())),rounded(source.fLeading), rounded(average),rounded(maximum),32,255,31,32,uint8_t(fixed ? 0x36 : 0x27)}; } std::vector TextRenderer::logical_advances(std::span definition,std::span text) { auto font=state->logical(definition); const auto ids=logical_glyphs(font,text); std::vector widths(ids.size(),0); if (!ids.empty()) font.getWidths(ids,widths); std::vector result; result.reserve(ids.size()); // Prefixes round the running float sum, like the qualified measurement, so widths never drift per glyph. float total=0; for (size_t i=0;i=32 && !(text[i]>=0xdc00 && text[i]<=0xdfff)) total+=widths[i]; result.push_back(int32_t(std::lround(total))); } return result; } std::vector TextRenderer::render(std::span definition,std::span text, uint32_t width,uint32_t height,int32_t origin_x,int32_t origin_y,std::span offsets) { if (!width || !height || width>16384 || height>16384 || uint64_t(width)*height>32*1024*1024) throw uint32_t(87); auto font=state->logical(definition); const auto ids=logical_glyphs(font,text); std::vector alpha(size_t(width)*height); SkBitmap bitmap; if (!bitmap.installPixels(SkImageInfo::MakeA8(int(width),int(height)),alpha.data(),width)) throw std::bad_alloc(); SkCanvas canvas(bitmap); canvas.clear(SK_ColorTRANSPARENT); SkPaint paint; paint.setColor(SK_ColorBLACK); paint.setAntiAlias(font.getEdging()!=SkFont::Edging::kAlias); SkFontMetrics source; font.getMetrics(&source); const auto baseline=SkScalar(origin_y)+SkScalar(std::lround(-source.fAscent)); std::vector widths(ids.size(),0); if (!ids.empty()) font.getWidths(ids,widths); std::vector drawn; std::vector points; SkScalar cursor=0; for (size_t i=0;i=32 && !(text[i]>=0xdc00 && text[i]<=0xdfff) ? SkScalar(std::lround(widths[i])) : 0; if (text[i]>=32 && ids[i]) { drawn.push_back(ids[i]); points.push_back({SkScalar(origin_x)+(offsets.empty() ? cursor : SkScalar(offsets[i])),baseline}); } cursor+=step; } if (!drawn.empty()) canvas.drawGlyphs({drawn.data(),drawn.size()},{points.data(),points.size()},{0,0},font,paint); return alpha; } }