#include "platform_abstraction.hpp" #include #include #include "../paint/truetype.hpp" #ifdef NANA_WINDOWS # ifndef _WIN32_WINNT # define _WIN32_WINNT 0x0501 # endif # include /////////////////////////////////////////////////////////////////////////////////////////////////////// /****************************************************************** * * * VersionHelpers.h -- This module defines helper functions to * * promote version check with proper * * comparisons. * * * * Copyright (c) Microsoft Corp. All rights reserved. * * * ******************************************************************/ #include // for _In_, etc. #if !defined(__midl) && !defined(SORTPP_PASS) #if (NTDDI_VERSION >= NTDDI_WINXP) #ifdef __cplusplus #define VERSIONHELPERAPI inline bool #else // __cplusplus #define VERSIONHELPERAPI FORCEINLINE BOOL #endif // __cplusplus VERSIONHELPERAPI IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor) { OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, { 0 }, 0, 0 }; DWORDLONG const dwlConditionMask = VerSetConditionMask( VerSetConditionMask( VerSetConditionMask( 0, VER_MAJORVERSION, VER_GREATER_EQUAL), VER_MINORVERSION, VER_GREATER_EQUAL), VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); osvi.dwMajorVersion = wMajorVersion; osvi.dwMinorVersion = wMinorVersion; osvi.wServicePackMajor = wServicePackMajor; return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE; } #ifndef _WIN32_WINNT_WINXP # define _WIN32_WINNT_WINXP 0x0501 #endif // _WIN32_WINNT_WINXP #ifndef _WIN32_WINNT_VISTA # define _WIN32_WINNT_VISTA 0x0600 #endif // _WIN32_WINNT_VISTA #ifndef _WIN32_WINNT_WIN7 # define _WIN32_WINNT_WIN7 0x0601 #endif // _WIN32_WINNT_WIN7 VERSIONHELPERAPI IsWindowsXPOrGreater() { return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 0); } VERSIONHELPERAPI IsWindowsXPSP1OrGreater() { return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 1); } VERSIONHELPERAPI IsWindowsXPSP2OrGreater() { return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 2); } VERSIONHELPERAPI IsWindowsXPSP3OrGreater() { return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 3); } VERSIONHELPERAPI IsWindowsVistaOrGreater() { return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 0); } VERSIONHELPERAPI IsWindowsVistaSP1OrGreater() { return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 1); } VERSIONHELPERAPI IsWindowsVistaSP2OrGreater() { return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 2); } VERSIONHELPERAPI IsWindows7OrGreater() { return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), LOBYTE(_WIN32_WINNT_WIN7), 0); } VERSIONHELPERAPI IsWindows7SP1OrGreater() { return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), LOBYTE(_WIN32_WINNT_WIN7), 1); } #ifndef _WIN32_WINNT_WIN8 // (0x0602) #define _WIN32_WINNT_WIN8 (0x0602) #endif // _WIN32_WINNT_WIN8(0x0602) VERSIONHELPERAPI IsWindows8OrGreater() { return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0); } #ifndef _WIN32_WINNT_WINBLUE // (0x0602) #define _WIN32_WINNT_WINBLUE (0x0602) #endif // _WIN32_WINNT_WINBLUE (0x0602) #endif // NTDDI_VERSION #endif // defined(__midl) #else # include "posix/platform_spec.hpp" # include # if defined(NANA_USE_XFT) # include # include # include # endif #endif namespace nana { #ifdef NANA_USE_XFT //A fallback fontset provides the multiple languages support. class fallback_fontset { public: fallback_fontset(): disp_(::nana::detail::platform_spec::instance().open_display()) { } ~fallback_fontset() { for(auto xft: xftset_) ::XftFontClose(disp_, xft); } void open(const std::string& font_desc, const std::set& langs) { for(auto xft: xftset_) ::XftFontClose(disp_, xft); xftset_.clear(); std::set loaded; for(auto & lang : langs) { std::string patstr = "*" + font_desc + ":lang=" + lang; auto pat = ::XftNameParse(patstr.c_str()); XftResult res; auto match_pat = ::XftFontMatch(disp_, ::XDefaultScreen(disp_), pat, &res); if (match_pat) { char * sf; if(XftResultTypeMismatch != ::XftPatternGetString(match_pat, "family", 0, &sf)) { //Avoid loading a some font repeatedly if(loaded.count(sf)) continue; } auto xft = ::XftFontOpenPattern(disp_, match_pat); if(xft) xftset_.push_back(xft); } } } int draw(::XftDraw* xftdraw, ::XftColor * xftcolor, ::XftFont* xft, int x, int y, const wchar_t* str, std::size_t len) { if(nullptr == str || 0 == len) return 0; int const init_x = x; std::unique_ptr glyph_indexes(new FT_UInt[len]); while(true) { auto preferred = _m_scan_fonts(xft, str, len, glyph_indexes.get()); x += _m_draw(xftdraw, xftcolor, preferred.first, x, y, str, preferred.second, glyph_indexes.get()); if(len == preferred.second) break; len -= preferred.second; str += preferred.second; } return x - init_x; } std::unique_ptr glyph_pixels(::XftFont* xft, const wchar_t* str, std::size_t len) { if(nullptr == xft || nullptr == str || 0 == len) return {}; std::unique_ptr glyph_indexes{new FT_UInt[len]}; std::unique_ptr pxbuf{new unsigned[len]}; auto pbuf = pxbuf.get(); auto pstr = str; auto size = len; while(true) { auto preferred = _m_scan_fonts(xft, pstr, size, glyph_indexes.get()); _m_glyph_px(preferred.first, pstr, preferred.second, glyph_indexes.get(), pbuf); if(size == preferred.second) break; size -= preferred.second; pstr += preferred.second; pbuf += preferred.second; } return pxbuf; } nana::size extents(::XftFont* xft, const wchar_t* str, std::size_t len) { nana::size extent; if(nullptr == str || 0 == len) return extent; std::unique_ptr glyph_indexes(new FT_UInt[len]); while(len > 0) { auto preferred = _m_scan_fonts(xft, str, len, glyph_indexes.get()); extent.width += _m_extents(preferred.first, str, preferred.second, glyph_indexes.get()); if(preferred.first->ascent + preferred.first->descent > static_cast(extent.height)) extent.height = preferred.first->ascent + preferred.first->descent; len -= preferred.second; str += preferred.second; } return extent; } private: //Tab is a invisible character int _m_draw(::XftDraw* xftdraw, ::XftColor* xftcolor, ::XftFont* xft, int x, int y, const wchar_t* str, std::size_t len, const FT_UInt* glyph_indexes) { int const init_x = x; auto p = str; auto const end = str + len; y += xft->ascent; ::XGlyphInfo ext; while(p < end) { auto off = p - str; auto ptab = _m_find_tab(p, end); if(ptab == p) { ++p; //x += static_cast(tab_pixels_); continue; } auto const size = ptab - p; ::XftDrawGlyphs(xftdraw, xftcolor, xft, x, y, glyph_indexes + off, size); ::XftGlyphExtents(disp_, xft, glyph_indexes + off, size, &ext); x += ext.xOff; if(ptab == end) break; p = ptab + 1; } return x - init_x; } //Tab is a invisible character unsigned _m_extents(::XftFont* xft, const wchar_t* const str, const std::size_t len, const FT_UInt* glyph_indexes) { unsigned pixels = 0; auto p = str; auto const end = str + len; ::XGlyphInfo ext; while(p < end) { auto off = p - str; auto ptab = _m_find_tab(p, end); if(ptab == p) { ++p; //extents->xOff += tab_pixels_; continue; } ::XftGlyphExtents(disp_, xft, glyph_indexes + off, ptab - p, &ext); pixels += ext.xOff; if(end == ptab) break; p = ptab + 1; } return pixels; } //Tab is a invisible character void _m_glyph_px(::XftFont* xft, const wchar_t* str, std::size_t len, const FT_UInt* glyph_indexes, unsigned* pxbuf) { auto const end = str + len; ::XGlyphInfo extent; for(auto p = str; p < end; ++p) { if('\t' != *p) { ::XftGlyphExtents(disp_, xft, glyph_indexes, 1, &extent); *pxbuf = extent.xOff; } else *pxbuf = 0;//tab_pixels_; ++glyph_indexes; } } static const wchar_t* _m_find_tab(const wchar_t* begin, const wchar_t* end) { while(begin < end) { if('\t' == *begin) return begin; ++begin; } return end; } std::pair<::XftFont*, std::size_t> _m_scan_fonts(::XftFont* xft, const wchar_t* str, std::size_t len, FT_UInt* const glyphs) const { auto preferred = xft; auto idx = ::XftCharIndex(disp_, xft, *str); if(0 == idx) { for(auto ft : xftset_) { idx = ::XftCharIndex(disp_, ft, *str); if(idx) { preferred = ft; break; } } } *glyphs = idx; if(0 == idx) { //scan the str with all fonts until a char index is found. for(std::size_t i = 1; i < len; ++i) { if(::XftCharIndex(disp_, xft, str[i])) return {preferred, i}; for(auto ft : xftset_) { if(::XftCharIndex(disp_, ft, str[i])) return {preferred, i}; } glyphs[i] = 0; } return {preferred, len}; } //scan the str with preferred font until a char index is invalid. for(std::size_t i = 1; i < len; ++i) { idx = ::XftCharIndex(disp_, preferred, str[i]); if(0 == idx) return {preferred, i}; glyphs[i] = idx; } return {preferred, len}; } private: Display* const disp_; std::vector<::XftFont*> xftset_; }; /// Fallback fontset manager class fallback_manager { public: fallback_manager(): langs_(_m_split_lang("ar,hi,zh-cn,zh-tw,ja,ko,th")) { } void languages(const std::string& lang) { langs_ = _m_split_lang(lang); for(auto & xft : xft_table_) { xft.second->open(xft.first, langs_); } } std::shared_ptr make_fallback(const std::string& font_desc) { auto i = xft_table_.find(font_desc); if(i != xft_table_.end()) return i->second; auto fb = std::make_shared(); fb->open(font_desc, langs_); xft_table_[font_desc] = fb; return fb; } void release_fallback(std::shared_ptr& p) { for(auto i = xft_table_.cbegin(); i != xft_table_.cend(); ++i) { if(i->second == p) { if(p.use_count() <= 2) xft_table_.erase(i); break; } } } private: static std::set _m_split_lang(const std::string& lang) { std::set langs; std::size_t start_pos = 0; while(true) { auto pos = lang.find(',', start_pos); auto l = lang.substr(start_pos, lang.npos == pos? lang.npos : pos - start_pos); if(!l.empty()) langs.insert(l); if(lang.npos == pos) break; start_pos = pos + 1; } return langs; } private: std::set langs_; std::map> xft_table_; }; #endif struct platform_runtime { std::shared_ptr font; #ifdef NANA_X11 std::map fontconfig_counts; #endif #ifdef NANA_USE_XFT fallback_manager fb_manager; #endif }; namespace { namespace data { static platform_runtime* storage; } } static platform_runtime& platform_storage() { if (nullptr == data::storage) throw std::runtime_error("platform_abstraction is empty"); return *data::storage; } class internal_font : public font_interface { public: using path_type = std::filesystem::path; #ifdef NANA_USE_XFT internal_font(const path_type& ttf, const std::string& font_family, double font_size, const font_style& fs, native_font_type native_font, std::shared_ptr fallback): ttf_(ttf), family_(font_family), size_(font_size), style_(fs), native_handle_(native_font), fallback_(fallback) {} #else internal_font(const path_type& ttf, const std::string& font_family, double font_size, const font_style& fs, native_font_type native_font): ttf_(ttf), family_(font_family), size_(font_size), style_(fs), native_handle_(native_font) {} #endif ~internal_font() { #ifdef NANA_WINDOWS ::DeleteObject(reinterpret_cast(native_handle_)); #elif defined(NANA_X11) auto disp = ::nana::detail::platform_spec::instance().open_display(); # ifdef NANA_USE_XFT platform_storage().fb_manager.release_fallback(fallback_); ::XftFontClose(disp, reinterpret_cast(native_handle_)); # else ::XFreeFontSet(disp, reinterpret_cast(native_handle_)); # endif #endif if (!ttf_.empty()) platform_abstraction::font_resource(false, ttf_); } public: const std::string& family() const override { return family_; } double size() const override { return size_; } const font_style & style() const override { return style_; } native_font_type native_handle() const override { return native_handle_; } #ifdef NANA_USE_XFT fallback_fontset* fallback() const { return fallback_.get(); } #endif private: path_type const ttf_; std::string const family_; double const size_; font_style const style_; native_font_type const native_handle_; #ifdef NANA_USE_XFT std::shared_ptr fallback_; #endif }; #ifdef NANA_USE_XFT void nana_xft_draw_string(::XftDraw* xftdraw, ::XftColor* xftcolor, font_interface* ft, const nana::point& pos, const wchar_t * str, std::size_t len) { auto fallback = static_cast(ft)->fallback(); if(nullptr == fallback) return; auto xft = reinterpret_cast(static_cast(ft)->native_handle()); fallback->draw(xftdraw, xftcolor, xft, pos.x, pos.y, str, len); } nana::size nana_xft_extents(font_interface* ft, const wchar_t* str, std::size_t len) { auto fallback = static_cast(ft)->fallback(); if(nullptr == fallback) return {}; auto xft = reinterpret_cast(static_cast(ft)->native_handle()); return fallback->extents(xft, str, len); } std::unique_ptr nana_xft_glyph_pixels(font_interface* ft, const wchar_t* str, std::size_t len) { auto fallback = static_cast(ft)->fallback(); if(nullptr == fallback) return {}; auto xft = reinterpret_cast(static_cast(ft)->native_handle()); return fallback->glyph_pixels(xft, str, len); } #endif void platform_abstraction::initialize() { if (nullptr == data::storage) data::storage = new platform_runtime; } void platform_abstraction::shutdown() { auto & r = platform_storage(); if (r.font.use_count() > 1) throw std::runtime_error("platform_abstraction is disallowed to shutdown"); r.font.reset(); delete data::storage; data::storage = nullptr; } double platform_abstraction::font_default_pt() { #ifdef NANA_WINDOWS //Create default font object. NONCLIENTMETRICS metrics = {}; metrics.cbSize = sizeof metrics; #if(WINVER >= 0x0600) #if defined(NANA_MINGW) OSVERSIONINFO osvi = {}; osvi.dwOSVersionInfoSize = sizeof(osvi); ::GetVersionEx(&osvi); if (osvi.dwMajorVersion < 6) metrics.cbSize -= sizeof(metrics.iPaddedBorderWidth); #else if (!IsWindowsVistaOrGreater()) metrics.cbSize -= sizeof(metrics.iPaddedBorderWidth); #endif #endif ::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof metrics, &metrics, 0); auto desktop = ::GetDC(nullptr); auto pt = std::abs(metrics.lfMessageFont.lfHeight) * 72.0 / ::GetDeviceCaps(desktop, LOGPIXELSY); ::ReleaseDC(nullptr, desktop); return pt; #else return 10; #endif } void platform_abstraction::font_languages(const std::string& langs) { #ifdef NANA_USE_XFT platform_storage().fb_manager.languages(langs); #endif } ::std::shared_ptr platform_abstraction::default_font(const ::std::shared_ptr& new_font) { auto & r = platform_storage(); if (new_font) { auto f = r.font; if (new_font != r.font) r.font = new_font; return f; } if (!r.font) r.font = make_font({}, 0, {}); return r.font; } static std::shared_ptr font_factory(::std::string font_family, double size_pt, const platform_abstraction::font::font_style& fs, internal_font::path_type ttf) { using native_font_type = platform_abstraction::font::native_font_type; #ifdef NANA_WINDOWS std::wstring wfont_family = to_nstring(font_family); //Make sure the length of font family less than LF_FACESIZE which is defined by Windows if (wfont_family.length() + 1 > LF_FACESIZE) wfont_family.clear(); //Translate pt to px auto hDC = ::GetDC(nullptr); auto font_height = -static_cast(size_pt * ::GetDeviceCaps(hDC, LOGPIXELSY) / 72); ::ReleaseDC(nullptr, hDC); if (wfont_family.empty() || (0 == font_height)) { //Create default font object. NONCLIENTMETRICS metrics = {}; metrics.cbSize = sizeof metrics; #if(WINVER >= 0x0600) #if defined(NANA_MINGW) OSVERSIONINFO osvi = {}; osvi.dwOSVersionInfoSize = sizeof(osvi); ::GetVersionEx(&osvi); if (osvi.dwMajorVersion < 6) metrics.cbSize -= sizeof(metrics.iPaddedBorderWidth); #else if (!IsWindowsVistaOrGreater()) metrics.cbSize -= sizeof(metrics.iPaddedBorderWidth); #endif #endif ::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof metrics, &metrics, 0); if (wfont_family.empty()) { wfont_family = metrics.lfMessageFont.lfFaceName; font_family = to_utf8(wfont_family); } if (0 == font_height) font_height = metrics.lfMessageFont.lfHeight; } ::LOGFONT lf{}; std::wcscpy(lf.lfFaceName, wfont_family.c_str()); lf.lfHeight = font_height; lf.lfCharSet = DEFAULT_CHARSET; lf.lfWeight = fs.weight; lf.lfQuality = PROOF_QUALITY; lf.lfPitchAndFamily = FIXED_PITCH; lf.lfItalic = fs.italic; lf.lfUnderline = fs.underline; lf.lfStrikeOut = fs.strike_out; auto fd = ::CreateFontIndirect(&lf); #elif defined(NANA_X11) auto disp = ::nana::detail::platform_spec::instance().open_display(); # ifdef NANA_USE_XFT if(font_family.empty()) font_family = "*"; std::string pat_str = '-' + std::to_string(size_pt ? size_pt : platform_abstraction::font_default_pt()); if(fs.weight < 400) pat_str += ":light"; else if(400 == fs.weight) pat_str += ":medium"; else if(fs.weight < 700) pat_str += ":demibold"; else pat_str += (700 == fs.weight ? ":bold": ":black"); if(fs.italic) pat_str += ":slant=italic"; auto pat = ::XftNameParse((font_family + pat_str).c_str()); XftResult res; auto match_pat = ::XftFontMatch(disp, ::XDefaultScreen(disp), pat, &res); ::XftFont* fd = nullptr; if (match_pat) fd = ::XftFontOpenPattern(disp, match_pat); # else std::string pat_str; if (font_family.empty()) pat_str = "-misc-fixed-*"; else pat_str = "-misc-fixed-" + font_family; char ** missing_list; int missing_count; char * defstr; XFontSet fd = ::XCreateFontSet(display_, const_cast(pat_str.c_str()), &missing_list, &missing_count, &defstr); # endif #endif if (fd) { #ifdef NANA_USE_XFT auto fallback = platform_storage().fb_manager.make_fallback(pat_str); return std::make_shared(std::move(ttf), std::move(font_family), size_pt, fs, reinterpret_cast(fd), fallback); #else return std::make_shared(std::move(ttf), std::move(font_family), size_pt, fs, reinterpret_cast(fd)); #endif } return{}; } ::std::shared_ptr platform_abstraction::make_font(const std::string& font_family, double size_pt, const font::font_style& fs) { return font_factory(font_family, size_pt, fs, {}); } ::std::shared_ptr platform_abstraction::make_font_from_ttf(const path_type& ttf, double size_pt, const font::font_style& fs) { ::nana::spec::truetype truetype{ ttf }; if (truetype.font_family().empty()) return nullptr; font_resource(true, ttf); return font_factory(truetype.font_family(), size_pt, fs, ttf); } void platform_abstraction::font_resource(bool try_add, const path_type& ttf) { #ifdef NANA_WINDOWS if (try_add) ::AddFontResourceEx(ttf.wstring().c_str(), FR_PRIVATE, nullptr); else ::RemoveFontResourceEx(ttf.wstring().c_str(), FR_PRIVATE, nullptr); #else auto & fc = platform_storage().fontconfig_counts; if(try_add) { if(1 == ++(fc[ttf.string()])) { ::FcConfigAppFontAddFile(nullptr, reinterpret_cast(ttf.string().c_str())); } } else { auto i = fc.find(ttf.string()); if(i != fc.end()) { if(0 == --(i->second)) fc.erase(i); if(0 == fc.size()) ::FcConfigAppFontClear(nullptr); } } #endif } unsigned platform_abstraction::screen_dpi(bool x_requested) { #ifdef NANA_WINDOWS auto hdc = ::GetDC(nullptr); auto dots = static_cast(::GetDeviceCaps(hdc, (x_requested ? LOGPIXELSX : LOGPIXELSY))); ::ReleaseDC(nullptr, hdc); return dots; #else auto & spec = ::nana::detail::platform_spec::instance(); auto disp = spec.open_display(); auto screen = ::XDefaultScreen(disp); double dots = 0.5; if (x_requested) dots += ((((double)DisplayWidth(disp, screen)) * 25.4) / ((double)DisplayWidthMM(disp, screen))); else dots += ((((double)DisplayHeight(disp, screen)) * 25.4) / ((double)DisplayHeightMM(disp, screen))); return static_cast(dots); #endif } }