From e5a6abd21595bb098f0de06f5cdaab75ede3edff Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 30 Oct 2015 20:57:08 -0700 Subject: [PATCH] Add a GDI-based font selector for libass This deliberately doesn't perform any font substitutions as the sort of people that use libass on Windows tend to perfer to manually pick fonts with the correctly glyphs. --- build/libass/config.h | 1 + build/libass/libass.vcxproj | 3 +- build/libass/libass.vcxproj.filters | 26 +++--- src/libass_gdi_fontselect.cpp | 120 ++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 17 deletions(-) create mode 100644 src/libass_gdi_fontselect.cpp diff --git a/build/libass/config.h b/build/libass/config.h index fda7ef5cf..d2fc12117 100644 --- a/build/libass/config.h +++ b/build/libass/config.h @@ -3,3 +3,4 @@ #define CONFIG_ASM 1 #define CONFIG_RASTERIZER 1 +#define CONFIG_DIRECTWRITE 1 diff --git a/build/libass/libass.vcxproj b/build/libass/libass.vcxproj index df487b6b8..61408f354 100644 --- a/build/libass/libass.vcxproj +++ b/build/libass/libass.vcxproj @@ -96,6 +96,7 @@ + @@ -107,4 +108,4 @@ {fb8e8d19-a4d6-4181-943c-282075f49b41} - + \ No newline at end of file diff --git a/build/libass/libass.vcxproj.filters b/build/libass/libass.vcxproj.filters index 2ef6efedf..db35379ba 100644 --- a/build/libass/libass.vcxproj.filters +++ b/build/libass/libass.vcxproj.filters @@ -19,9 +19,6 @@ - - Assembly Files - Assembly Files @@ -49,6 +46,9 @@ Assembly Files + + Assembly Files + @@ -87,22 +87,13 @@ Header Files - + Header Files - + Header Files - - Header Files - - - Header Files - - - Header Files - - + Header Files @@ -158,5 +149,8 @@ Source Files + + Source Files + - + \ No newline at end of file diff --git a/src/libass_gdi_fontselect.cpp b/src/libass_gdi_fontselect.cpp new file mode 100644 index 000000000..3623968e5 --- /dev/null +++ b/src/libass_gdi_fontselect.cpp @@ -0,0 +1,120 @@ +// Copyright (c) 2016, Thomas Goyne +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +extern "C" { +#include "ass_fontselect.h" +} + +#undef inline + +#include + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +namespace { +class GdiFont { + HFONT font; + std::shared_ptr dc; + + size_t size = 0; + std::unique_ptr font_data; + +public: + GdiFont(HFONT font, std::shared_ptr dc) : font(font), dc(dc) { } + ~GdiFont() { DeleteObject(font); } + + size_t GetData(unsigned char *data, size_t offset, size_t len); + bool CheckPostscript() { return false; } + bool CheckGlyph(uint32_t codepoint) { return true; } + void Destroy() { delete this; } +}; + +size_t GdiFont::GetData(unsigned char *data, size_t offset, size_t len) { + if (!font_data) { + SelectObject(dc.get(), font); + size = GetFontData(dc.get(), 0, 0, 0, 0); + if (size == GDI_ERROR) + return 0; + font_data.reset(new char[size]); + GetFontData(dc.get(), 0, 0, font_data.get(), size); + } + + if (!data) + return size; + memcpy(data, font_data.get() + offset, len); + return len; +} + +void match_fonts(ASS_Library *lib, ASS_FontProvider *provider, char *name) { + std::shared_ptr dc(CreateCompatibleDC(nullptr), [](HDC dc) { DeleteDC(dc); }); + + LOGFONTW lf{}; + lf.lfCharSet = DEFAULT_CHARSET; + MultiByteToWideChar(CP_UTF8, 0, name, -1, lf.lfFaceName, LF_FACESIZE); + auto cb = [=](LOGFONTW const& lf) { + ASS_FontProviderMetaData meta{}; + meta.weight = lf.lfWeight; + meta.slant = lf.lfItalic ? FONT_SLANT_ITALIC : FONT_SLANT_NONE; + meta.width = FONT_WIDTH_NORMAL; + + meta.families= static_cast(malloc(sizeof(char *))); + meta.n_family = 1; + + auto name = static_cast(malloc(LF_FACESIZE * 4)); + auto len = wcsnlen(lf.lfFaceName, LF_FACESIZE); + auto written = WideCharToMultiByte(CP_UTF8, 0, lf.lfFaceName, len, + name, LF_FACESIZE * 4, nullptr, nullptr); + name[written] = 0; + meta.families[0] = name; + + auto hfont = CreateFontIndirectW(&lf); + ass_font_provider_add_font(provider, &meta, nullptr, 0, new GdiFont(hfont, dc)); + }; + using type = decltype(cb); + EnumFontFamiliesEx(dc.get(), &lf, [](const LOGFONT *lf, const TEXTMETRIC *, DWORD, LPARAM lParam) -> int { + (*reinterpret_cast(lParam))(*lf); + return 1; + }, (LPARAM)&cb, 0); +} + +template struct wrapper; +template +struct wrapper { + static R call(void *obj, Args... args) { + return (static_cast(obj)->*method)(args...); + } +}; +} + +extern "C" +ASS_FontProvider *ass_directwrite_add_provider(ASS_Library *, + ASS_FontSelector *selector, + const char *) { +#define WRAP(method) &wrapper::call + static ASS_FontProviderFuncs callbacks = { + WRAP(GdiFont::GetData), + WRAP(GdiFont::CheckPostscript), + WRAP(GdiFont::CheckGlyph), + WRAP(GdiFont::Destroy), + nullptr, // destroy_provider + &match_fonts, + nullptr, // get_substitution + nullptr, // get_fallback + }; + return ass_font_provider_new(selector, &callbacks, nullptr); +}