923 lines
33 KiB
C++
923 lines
33 KiB
C++
#include "Emboss.hpp"
|
|
#include <stdio.h>
|
|
#include <cstdlib>
|
|
#include <boost/nowide/convert.hpp>
|
|
#include <ClipperUtils.hpp> // union_ex
|
|
|
|
#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation
|
|
#include "imgui/imstb_truetype.h" // stbtt_fontinfo
|
|
#include "Utils.hpp" // ScopeGuard
|
|
|
|
#include <Triangulation.hpp> // CGAL project
|
|
#include "libslic3r.h"
|
|
|
|
#include "ClipperUtils.hpp" // for boldness - polygon extend(offset)
|
|
|
|
using namespace Slic3r;
|
|
|
|
double Emboss::SHAPE_SCALE = 0.001;//SCALING_FACTOR;
|
|
|
|
// do not expose out of this file stbtt_ data types
|
|
class Private
|
|
{
|
|
public:
|
|
Private() = delete;
|
|
static bool is_valid(const Emboss::FontFile &font, unsigned int index);
|
|
static std::optional<stbtt_fontinfo> load_font_info(const unsigned char *data, unsigned int index = 0);
|
|
static std::optional<Emboss::Glyph> get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness);
|
|
|
|
// take glyph from cache
|
|
static const Emboss::Glyph* get_glyph(int unicode, const Emboss::FontFile &font, const FontProp &font_prop,
|
|
Emboss::Glyphs &cache, std::optional<stbtt_fontinfo> &font_info_opt);
|
|
|
|
static FontItem create_font_item(std::wstring name, std::wstring path);
|
|
|
|
/// <summary>
|
|
/// TODO: move to ExPolygon utils
|
|
/// Remove multi points. When exist multi point dilate it by rect 3x3 and union result.
|
|
/// </summary>
|
|
/// <param name="expolygons">Shape which can contain same point, will be extended by dilatation rects</param>
|
|
/// <returns>ExPolygons with only unique points</returns>
|
|
static ExPolygons dilate_to_unique_points(ExPolygons &expolygons);
|
|
|
|
// scale and convert float to int coordinate
|
|
static Point to_point(const stbtt__point &point);
|
|
};
|
|
|
|
bool Private::is_valid(const Emboss::FontFile &font, unsigned int index) {
|
|
if (font.data == nullptr) return false;
|
|
if (font.data->empty()) return false;
|
|
if (index >= font.infos.size()) return false;
|
|
return true;
|
|
}
|
|
|
|
std::optional<stbtt_fontinfo> Private::load_font_info(
|
|
const unsigned char *data, unsigned int index)
|
|
{
|
|
int font_offset = stbtt_GetFontOffsetForIndex(data, index);
|
|
if (font_offset < 0) {
|
|
assert(false);
|
|
// "Font index(" << index << ") doesn't exist.";
|
|
return {};
|
|
}
|
|
stbtt_fontinfo font_info;
|
|
if (stbtt_InitFont(&font_info, data, font_offset) == 0) {
|
|
// Can't initialize font.
|
|
assert(false);
|
|
return {};
|
|
}
|
|
return font_info;
|
|
}
|
|
|
|
std::optional<Emboss::Glyph> Private::get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness)
|
|
{
|
|
int glyph_index = stbtt_FindGlyphIndex(&font_info, unicode_letter);
|
|
if (glyph_index == 0) {
|
|
//wchar_t wchar = static_cast<wchar_t>(unicode_letter);
|
|
//<< "Character unicode letter ("
|
|
//<< "decimal value = " << std::dec << unicode_letter << ", "
|
|
//<< "hexadecimal value = U+" << std::hex << unicode_letter << std::dec << ", "
|
|
//<< "wchar value = " << wchar
|
|
//<< ") is NOT defined inside of the font. \n";
|
|
return {};
|
|
}
|
|
|
|
Emboss::Glyph glyph;
|
|
stbtt_GetGlyphHMetrics(&font_info, glyph_index, &glyph.advance_width, &glyph.left_side_bearing);
|
|
|
|
stbtt_vertex *vertices;
|
|
int num_verts = stbtt_GetGlyphShape(&font_info, glyph_index, &vertices);
|
|
if (num_verts <= 0) return glyph; // no shape
|
|
ScopeGuard sg1([&vertices]() { free(vertices); });
|
|
|
|
int *contour_lengths = NULL;
|
|
int num_countour_int = 0;
|
|
stbtt__point *points = stbtt_FlattenCurves(vertices, num_verts,
|
|
flatness, &contour_lengths, &num_countour_int, font_info.userdata);
|
|
if (!points) return glyph; // no valid flattening
|
|
ScopeGuard sg2([&contour_lengths, &points]() {
|
|
free(contour_lengths);
|
|
free(points);
|
|
});
|
|
|
|
size_t num_contour = static_cast<size_t>(num_countour_int);
|
|
Polygons glyph_polygons;
|
|
glyph_polygons.reserve(num_contour);
|
|
size_t pi = 0; // point index
|
|
for (size_t ci = 0; ci < num_contour; ++ci) {
|
|
int length = contour_lengths[ci];
|
|
// check minimal length for triangle
|
|
if (length < 4) {
|
|
// weird font
|
|
pi+=length;
|
|
continue;
|
|
}
|
|
// last point is first point
|
|
--length;
|
|
Points pts;
|
|
pts.reserve(length);
|
|
for (int i = 0; i < length; ++i)
|
|
pts.emplace_back(to_point(points[pi++]));
|
|
|
|
// last point is first point --> closed contour
|
|
assert(pts.front() == to_point(points[pi]));
|
|
++pi;
|
|
|
|
// change outer cw to ccw and inner ccw to cw order
|
|
std::reverse(pts.begin(), pts.end());
|
|
glyph_polygons.emplace_back(pts);
|
|
}
|
|
|
|
// fix for bad defined fonts
|
|
glyph.shape = Slic3r::union_ex(glyph_polygons);
|
|
|
|
// inner cw - hole
|
|
// outer ccw - contour
|
|
return glyph;
|
|
}
|
|
|
|
const Emboss::Glyph* Private::get_glyph(
|
|
int unicode,
|
|
const Emboss::FontFile & font,
|
|
const FontProp & font_prop,
|
|
Emboss::Glyphs & cache,
|
|
std::optional<stbtt_fontinfo> &font_info_opt)
|
|
{
|
|
const double RESOLUTION = 0.0125; // TODO: read from printer configuration
|
|
auto glyph_item = cache.find(unicode);
|
|
if (glyph_item != cache.end()) return &glyph_item->second;
|
|
|
|
unsigned int font_index = font_prop.collection_number.has_value()?
|
|
*font_prop.collection_number : 0;
|
|
if (!is_valid(font, font_index)) return nullptr;
|
|
|
|
if (!font_info_opt.has_value()) {
|
|
|
|
font_info_opt = Private::load_font_info(font.data->data(), font_index);
|
|
// can load font info?
|
|
if (!font_info_opt.has_value()) return nullptr;
|
|
}
|
|
|
|
float flatness = static_cast<float>(font.infos[font_index].ascent * RESOLUTION / font_prop.size_in_mm);
|
|
std::optional<Emboss::Glyph> glyph_opt =
|
|
Private::get_glyph(*font_info_opt, unicode, flatness);
|
|
|
|
// IMPROVE: multiple loadig glyph without data
|
|
// has definition inside of font?
|
|
if (!glyph_opt.has_value()) return nullptr;
|
|
|
|
if (font_prop.char_gap.has_value())
|
|
glyph_opt->advance_width += *font_prop.char_gap;
|
|
|
|
// scale glyph size
|
|
glyph_opt->advance_width =
|
|
static_cast<int>(glyph_opt->advance_width / Emboss::SHAPE_SCALE);
|
|
glyph_opt->left_side_bearing =
|
|
static_cast<int>(glyph_opt->left_side_bearing / Emboss::SHAPE_SCALE);
|
|
|
|
if (font_prop.boldness.has_value()) {
|
|
float delta = *font_prop.boldness / Emboss::SHAPE_SCALE / font_prop.size_in_mm;
|
|
glyph_opt->shape = offset_ex(glyph_opt->shape, delta);
|
|
}
|
|
|
|
if (font_prop.skew.has_value()) {
|
|
const float &ratio = *font_prop.skew;
|
|
auto skew = [&ratio](Slic3r::Polygon &polygon) {
|
|
for (Slic3r::Point &p : polygon.points) { p.x() += p.y() * ratio; }
|
|
};
|
|
for (ExPolygon &expolygon : glyph_opt->shape) {
|
|
skew(expolygon.contour);
|
|
for (Slic3r::Polygon &hole : expolygon.holes) skew(hole);
|
|
}
|
|
}
|
|
|
|
// union of shape
|
|
// (for sure) I do not believe in font corectness
|
|
// modification like bold or skew could create artefacts
|
|
glyph_opt->shape = Slic3r::union_ex(glyph_opt->shape);
|
|
// unify multipoints with similar position. Could appear after union
|
|
dilate_to_unique_points(glyph_opt->shape);
|
|
auto it = cache.insert({unicode, std::move(*glyph_opt)});
|
|
assert(it.second);
|
|
return &it.first->second;
|
|
}
|
|
|
|
FontItem Private::create_font_item(std::wstring name, std::wstring path) {
|
|
return { boost::nowide::narrow(name.c_str()),
|
|
boost::nowide::narrow(path.c_str()),
|
|
FontItem::Type::file_path, FontProp() };
|
|
}
|
|
|
|
ExPolygons Private::dilate_to_unique_points(ExPolygons &expolygons)
|
|
{
|
|
std::set<Point> points;
|
|
std::set<Point> multi_points;
|
|
auto find_multipoint = [&points, &multi_points](const Points &pts) {
|
|
for (const Point &p : pts) {
|
|
auto it = points.find(p);
|
|
if (it != points.end())
|
|
multi_points.insert(p);
|
|
else
|
|
points.insert(p);
|
|
}
|
|
};
|
|
for (const ExPolygon &expolygon : expolygons) {
|
|
find_multipoint(expolygon.contour.points);
|
|
for (const Slic3r::Polygon &hole : expolygon.holes)
|
|
find_multipoint(hole.points);
|
|
}
|
|
// speed up, no multipoints
|
|
if (multi_points.empty()) return expolygons;
|
|
|
|
// CCW rectangle around zero with size 3*3 px for dilatation
|
|
const Points rect_3_3{Point(1, 1), Point(-1, 1), Point(-1, -1), Point(1, -1)};
|
|
const Points rect_side{Point(1, 0), Point(0, 1), Point(-1, 0), Point(0, -1)};
|
|
|
|
// all new added points for reduction
|
|
std::set<Point> rects_points;
|
|
|
|
// extends expolygons with dilatation rectangle
|
|
expolygons.reserve(expolygons.size() + multi_points.size());
|
|
for (const Point &multi_point : multi_points) {
|
|
Slic3r::Polygon rect(rect_3_3); // copy points
|
|
rect.translate(multi_point);
|
|
for (const Point& p : rect.points) rects_points.insert(p);
|
|
// add side point to be sure with result
|
|
for (const Point& p : rect_side) rects_points.insert(p + multi_point);
|
|
expolygons.emplace_back(rect);
|
|
}
|
|
ExPolygons result = union_ex(expolygons);
|
|
|
|
// reduce new created close points
|
|
auto reduce_close_points = [&rects_points](Points &pts) {
|
|
bool is_first = false;
|
|
size_t offset = 0;
|
|
bool is_prev_rect = false;
|
|
for (size_t i = 0; i < pts.size(); i++) {
|
|
const Point &p = pts[i];
|
|
bool is_rect = (rects_points.find(p) != rects_points.end());
|
|
if (is_prev_rect && is_rect) ++offset;
|
|
if (offset != 0) pts[i - offset] = p;
|
|
if (i == 0 && is_rect) is_first = true;
|
|
is_prev_rect = is_rect;
|
|
}
|
|
// remove last
|
|
if (is_first && is_prev_rect) ++offset;
|
|
if (offset != 0)
|
|
pts.erase(pts.begin() + (pts.size() - offset), pts.end());
|
|
};
|
|
for (ExPolygon &expolygon : result) {
|
|
reduce_close_points(expolygon.contour.points);
|
|
for (Slic3r::Polygon &hole : expolygon.holes)
|
|
reduce_close_points(hole.points);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Point Private::to_point(const stbtt__point &point) {
|
|
return Point(static_cast<int>(std::round(point.x / Emboss::SHAPE_SCALE)),
|
|
static_cast<int>(std::round(point.y / Emboss::SHAPE_SCALE)));
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#include <wingdi.h>
|
|
#include <windef.h>
|
|
#include <WinUser.h>
|
|
|
|
// Get system font file path
|
|
std::optional<std::wstring> Emboss::get_font_path(const std::wstring &font_face_name)
|
|
{
|
|
static const LPWSTR fontRegistryPath = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts";
|
|
HKEY hKey;
|
|
LONG result;
|
|
|
|
// Open Windows font registry key
|
|
result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, fontRegistryPath, 0, KEY_READ, &hKey);
|
|
if (result != ERROR_SUCCESS) return {};
|
|
|
|
DWORD maxValueNameSize, maxValueDataSize;
|
|
result = RegQueryInfoKey(hKey, 0, 0, 0, 0, 0, 0, 0, &maxValueNameSize, &maxValueDataSize, 0, 0);
|
|
if (result != ERROR_SUCCESS) return {};
|
|
|
|
DWORD valueIndex = 0;
|
|
LPWSTR valueName = new WCHAR[maxValueNameSize];
|
|
LPBYTE valueData = new BYTE[maxValueDataSize];
|
|
DWORD valueNameSize, valueDataSize, valueType;
|
|
std::wstring wsFontFile;
|
|
|
|
// Look for a matching font name
|
|
do {
|
|
wsFontFile.clear();
|
|
valueDataSize = maxValueDataSize;
|
|
valueNameSize = maxValueNameSize;
|
|
|
|
result = RegEnumValue(hKey, valueIndex, valueName, &valueNameSize, 0, &valueType, valueData, &valueDataSize);
|
|
|
|
valueIndex++;
|
|
if (result != ERROR_SUCCESS || valueType != REG_SZ) {
|
|
continue;
|
|
}
|
|
|
|
std::wstring wsValueName(valueName, valueNameSize);
|
|
|
|
// Found a match
|
|
if (_wcsnicmp(font_face_name.c_str(), wsValueName.c_str(), font_face_name.length()) == 0) {
|
|
|
|
wsFontFile.assign((LPWSTR)valueData, valueDataSize);
|
|
break;
|
|
}
|
|
}while (result != ERROR_NO_MORE_ITEMS);
|
|
|
|
delete[] valueName;
|
|
delete[] valueData;
|
|
|
|
RegCloseKey(hKey);
|
|
|
|
if (wsFontFile.empty()) return {};
|
|
|
|
// Build full font file path
|
|
WCHAR winDir[MAX_PATH];
|
|
GetWindowsDirectory(winDir, MAX_PATH);
|
|
|
|
std::wstringstream ss;
|
|
ss << winDir << "\\Fonts\\" << wsFontFile;
|
|
wsFontFile = ss.str();
|
|
|
|
return wsFontFile;
|
|
}
|
|
|
|
FontList Emboss::get_font_list()
|
|
{
|
|
//FontList list1 = get_font_list_by_enumeration();
|
|
//FontList list2 = get_font_list_by_register();
|
|
//FontList list3 = get_font_list_by_folder();
|
|
return get_font_list_by_register();
|
|
}
|
|
|
|
FontList Emboss::get_font_list_by_register() {
|
|
static const LPWSTR fontRegistryPath = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts";
|
|
HKEY hKey;
|
|
LONG result;
|
|
|
|
// Open Windows font registry key
|
|
result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, fontRegistryPath, 0, KEY_READ, &hKey);
|
|
if (result != ERROR_SUCCESS) {
|
|
assert(false);
|
|
//std::wcerr << L"Can not Open register key (" << fontRegistryPath << ")"
|
|
// << L", function 'RegOpenKeyEx' return code: " << result << std::endl;
|
|
return {};
|
|
}
|
|
|
|
DWORD maxValueNameSize, maxValueDataSize;
|
|
result = RegQueryInfoKey(hKey, 0, 0, 0, 0, 0, 0, 0, &maxValueNameSize,
|
|
&maxValueDataSize, 0, 0);
|
|
if (result != ERROR_SUCCESS) {
|
|
assert(false);
|
|
// Can not earn query key, function 'RegQueryInfoKey' return code: result
|
|
return {};
|
|
}
|
|
|
|
// Build full font file path
|
|
WCHAR winDir[MAX_PATH];
|
|
GetWindowsDirectory(winDir, MAX_PATH);
|
|
std::wstring font_path = std::wstring(winDir) + L"\\Fonts\\";
|
|
|
|
FontList font_list;
|
|
DWORD valueIndex = 0;
|
|
// Look for a matching font name
|
|
LPWSTR font_name = new WCHAR[maxValueNameSize];
|
|
LPBYTE fileTTF_name = new BYTE[maxValueDataSize];
|
|
DWORD font_name_size, fileTTF_name_size, valueType;
|
|
do {
|
|
fileTTF_name_size = maxValueDataSize;
|
|
font_name_size = maxValueNameSize;
|
|
|
|
result = RegEnumValue(hKey, valueIndex, font_name, &font_name_size, 0,
|
|
&valueType, fileTTF_name, &fileTTF_name_size);
|
|
valueIndex++;
|
|
if (result != ERROR_SUCCESS || valueType != REG_SZ) continue;
|
|
std::wstring font_name_w(font_name, font_name_size);
|
|
std::wstring file_name_w((LPWSTR) fileTTF_name, fileTTF_name_size);
|
|
std::wstring path_w = font_path + file_name_w;
|
|
|
|
// filtrate .fon from lists
|
|
size_t pos = font_name_w.rfind(L" (TrueType)");
|
|
if (pos >= font_name_w.size()) continue;
|
|
// remove TrueType text from name
|
|
font_name_w = std::wstring(font_name_w, 0, pos);
|
|
font_list.emplace_back(Private::create_font_item(font_name_w, path_w));
|
|
} while (result != ERROR_NO_MORE_ITEMS);
|
|
delete[] font_name;
|
|
delete[] fileTTF_name;
|
|
|
|
RegCloseKey(hKey);
|
|
return font_list;
|
|
}
|
|
|
|
// TODO: Fix global function
|
|
bool CALLBACK EnumFamCallBack(LPLOGFONT lplf,
|
|
LPNEWTEXTMETRIC lpntm,
|
|
DWORD FontType,
|
|
LPVOID aFontList)
|
|
{
|
|
std::vector<std::wstring> *fontList =
|
|
(std::vector<std::wstring> *) (aFontList);
|
|
if (FontType & TRUETYPE_FONTTYPE) {
|
|
std::wstring name = lplf->lfFaceName;
|
|
fontList->push_back(name);
|
|
}
|
|
return true;
|
|
// UNREFERENCED_PARAMETER(lplf);
|
|
UNREFERENCED_PARAMETER(lpntm);
|
|
}
|
|
|
|
FontList Emboss::get_font_list_by_enumeration() {
|
|
|
|
HDC hDC = GetDC(NULL);
|
|
std::vector<std::wstring> font_names;
|
|
EnumFontFamilies(hDC, (LPCTSTR) NULL, (FONTENUMPROC) EnumFamCallBack,
|
|
(LPARAM) &font_names);
|
|
|
|
FontList font_list;
|
|
for (const std::wstring &font_name : font_names) {
|
|
font_list.emplace_back(Private::create_font_item(font_name, L""));
|
|
}
|
|
return font_list;
|
|
}
|
|
|
|
FontList Emboss::get_font_list_by_folder() {
|
|
FontList result;
|
|
WCHAR winDir[MAX_PATH];
|
|
UINT winDir_size = GetWindowsDirectory(winDir, MAX_PATH);
|
|
std::wstring search_dir = std::wstring(winDir, winDir_size) + L"\\Fonts\\";
|
|
WIN32_FIND_DATA fd;
|
|
HANDLE hFind;
|
|
// By https://en.wikipedia.org/wiki/TrueType has also suffix .tte
|
|
std::vector<std::wstring> suffixes = {L"*.ttf", L"*.ttc", L"*.tte"};
|
|
for (const std::wstring &suffix : suffixes) {
|
|
hFind = ::FindFirstFile((search_dir + suffix).c_str(), &fd);
|
|
if (hFind == INVALID_HANDLE_VALUE) continue;
|
|
do {
|
|
// skip folder . and ..
|
|
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue;
|
|
std::wstring file_name(fd.cFileName);
|
|
// TODO: find font name instead of filename
|
|
result.emplace_back(Private::create_font_item(file_name, search_dir + file_name));
|
|
} while (::FindNextFile(hFind, &fd));
|
|
::FindClose(hFind);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#else
|
|
FontList Emboss::get_font_list() {
|
|
// not implemented
|
|
return {};
|
|
}
|
|
|
|
std::optional<std::wstring> Emboss::get_font_path(const std::wstring &font_face_name){
|
|
// not implemented
|
|
return {};
|
|
}
|
|
#endif
|
|
|
|
std::unique_ptr<Emboss::FontFile> Emboss::create_font_file(
|
|
std::unique_ptr<std::vector<unsigned char>> data)
|
|
{
|
|
int collection_size = stbtt_GetNumberOfFonts(data->data());
|
|
// at least one font must be inside collection
|
|
if (collection_size < 1) {
|
|
assert(false);
|
|
// There is no font collection inside font data
|
|
return nullptr;
|
|
}
|
|
|
|
unsigned int c_size = static_cast<unsigned int>(collection_size);
|
|
std::vector<FontFile::Info> infos;
|
|
infos.reserve(c_size);
|
|
for (unsigned int i = 0; i < c_size; ++i) {
|
|
auto font_info = Private::load_font_info(data->data(), i);
|
|
if (!font_info.has_value()) return nullptr;
|
|
|
|
const stbtt_fontinfo *info = &(*font_info);
|
|
// load information about line gap
|
|
int ascent, descent, linegap;
|
|
stbtt_GetFontVMetrics(info, &ascent, &descent, &linegap);
|
|
|
|
float pixels = 1000.; // value is irelevant
|
|
float em_pixels = stbtt_ScaleForMappingEmToPixels(info, pixels);
|
|
int units_per_em = static_cast<int>(std::round(pixels / em_pixels));
|
|
|
|
infos.emplace_back(FontFile::Info{ascent, descent, linegap, units_per_em});
|
|
}
|
|
return std::make_unique<Emboss::FontFile>(std::move(data), std::move(infos));
|
|
}
|
|
|
|
std::unique_ptr<Emboss::FontFile> Emboss::create_font_file(const char *file_path)
|
|
{
|
|
FILE *file = fopen(file_path, "rb");
|
|
if (file == nullptr) {
|
|
assert(false);
|
|
// BOOST_LOG_TRIVIAL(error) << "Couldn't open " << file_path << " for reading." << std::endl;
|
|
return nullptr;
|
|
}
|
|
|
|
// find size of file
|
|
if (fseek(file, 0L, SEEK_END) != 0) {
|
|
assert(false);
|
|
// BOOST_LOG_TRIVIAL(error) << "Couldn't fseek file " << file_path << " for size measure." << std::endl;
|
|
return nullptr;
|
|
}
|
|
size_t size = ftell(file);
|
|
if (size == 0) {
|
|
assert(false);
|
|
// BOOST_LOG_TRIVIAL(error) << "Size of font file is zero. Can't read." << std::endl;
|
|
return nullptr;
|
|
}
|
|
rewind(file);
|
|
auto buffer = std::make_unique<std::vector<unsigned char>>(size);
|
|
size_t count_loaded_bytes = fread((void *) &buffer->front(), 1, size, file);
|
|
if (count_loaded_bytes != size) {
|
|
assert(false);
|
|
// BOOST_LOG_TRIVIAL(error) << "Different loaded(from file) data size." << std::endl;
|
|
return nullptr;
|
|
}
|
|
return create_font_file(std::move(buffer));
|
|
}
|
|
|
|
|
|
#ifdef _WIN32
|
|
static bool load_hfont(void* hfont, DWORD &dwTable, DWORD &dwOffset, size_t& size, HDC hdc = nullptr){
|
|
bool del_hdc = false;
|
|
if (hdc == nullptr) {
|
|
del_hdc = true;
|
|
hdc = ::CreateCompatibleDC(NULL);
|
|
if (hdc == NULL) return false;
|
|
}
|
|
|
|
// To retrieve the data from the beginning of the file for TrueType
|
|
// Collection files specify 'ttcf' (0x66637474).
|
|
dwTable = 0x66637474;
|
|
dwOffset = 0;
|
|
|
|
::SelectObject(hdc, hfont);
|
|
size = ::GetFontData(hdc, dwTable, dwOffset, NULL, 0);
|
|
if (size == GDI_ERROR) {
|
|
// HFONT is NOT TTC(collection)
|
|
dwTable = 0;
|
|
size = ::GetFontData(hdc, dwTable, dwOffset, NULL, 0);
|
|
}
|
|
|
|
if (size == 0 || size == GDI_ERROR) {
|
|
if (del_hdc) ::DeleteDC(hdc);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void * Emboss::can_load(HFONT hfont)
|
|
{
|
|
DWORD dwTable=0, dwOffset=0;
|
|
size_t size = 0;
|
|
if (!load_hfont(hfont, dwTable, dwOffset, size)) return nullptr;
|
|
return hfont;
|
|
}
|
|
|
|
std::unique_ptr<Emboss::FontFile> Emboss::create_font_file(HFONT hfont)
|
|
{
|
|
HDC hdc = ::CreateCompatibleDC(NULL);
|
|
if (hdc == NULL) {
|
|
assert(false);
|
|
// BOOST_LOG_TRIVIAL(error) << "Can't create HDC by CreateCompatibleDC(NULL)." << std::endl;
|
|
return nullptr;
|
|
}
|
|
|
|
DWORD dwTable=0,dwOffset = 0;
|
|
size_t size;
|
|
if (!load_hfont(hfont, dwTable, dwOffset, size, hdc)) {
|
|
::DeleteDC(hdc);
|
|
return nullptr;
|
|
}
|
|
auto buffer = std::make_unique<std::vector<unsigned char>>(size);
|
|
size_t loaded_size = ::GetFontData(hdc, dwTable, dwOffset, buffer->data(), size);
|
|
::DeleteDC(hdc);
|
|
if (size != loaded_size) {
|
|
assert(false);
|
|
// BOOST_LOG_TRIVIAL(error) << "Different loaded(from HFONT) data size." << std::endl;
|
|
return nullptr;
|
|
}
|
|
return create_font_file(std::move(buffer));
|
|
}
|
|
#endif // _WIN32
|
|
|
|
std::optional<Emboss::Glyph> Emboss::letter2glyph(const FontFile &font,
|
|
unsigned int font_index,
|
|
int letter,
|
|
float flatness)
|
|
{
|
|
if (!Private::is_valid(font, font_index)) return {};
|
|
auto font_info_opt = Private::load_font_info(font.data->data(), font_index);
|
|
if (!font_info_opt.has_value()) return {};
|
|
return Private::get_glyph(*font_info_opt, letter, flatness);
|
|
}
|
|
|
|
ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache,
|
|
const char * text,
|
|
const FontProp &font_prop)
|
|
{
|
|
assert(font_with_cache.has_value());
|
|
|
|
std::optional<stbtt_fontinfo> font_info_opt;
|
|
Point cursor(0, 0);
|
|
ExPolygons result;
|
|
const FontFile& font = *font_with_cache.font_file;
|
|
unsigned int font_index = font_prop.collection_number.has_value()?
|
|
*font_prop.collection_number : 0;
|
|
if (!Private::is_valid(font, font_index)) return {};
|
|
const FontFile::Info& info = font.infos[font_index];
|
|
Emboss::Glyphs& cache = *font_with_cache.cache;
|
|
std::wstring ws = boost::nowide::widen(text);
|
|
for (wchar_t wc: ws){
|
|
if (wc == '\n') {
|
|
int line_height = info.ascent - info.descent + info.linegap;
|
|
if (font_prop.line_gap.has_value())
|
|
line_height += *font_prop.line_gap;
|
|
line_height = static_cast<int>(line_height / SHAPE_SCALE);
|
|
|
|
cursor.x() = 0;
|
|
cursor.y() -= line_height;
|
|
continue;
|
|
}
|
|
if (wc == '\t') {
|
|
// '\t' = 4*space => same as imgui
|
|
const int count_spaces = 4;
|
|
const Glyph* space = Private::get_glyph(int(' '), font, font_prop, cache, font_info_opt);
|
|
if (space == nullptr) continue;
|
|
cursor.x() += count_spaces * space->advance_width;
|
|
continue;
|
|
}
|
|
if (wc == '\r') continue;
|
|
|
|
int unicode = static_cast<int>(wc);
|
|
const Glyph* glyph_ptr = Private::get_glyph(unicode, font, font_prop, cache, font_info_opt);
|
|
if (glyph_ptr == nullptr) continue;
|
|
|
|
// move glyph to cursor position
|
|
ExPolygons expolygons = glyph_ptr->shape; // copy
|
|
for (ExPolygon &expolygon : expolygons)
|
|
expolygon.translate(cursor);
|
|
|
|
cursor.x() += glyph_ptr->advance_width;
|
|
expolygons_append(result, std::move(expolygons));
|
|
}
|
|
result = Slic3r::union_ex(result);
|
|
return Private::dilate_to_unique_points(result);
|
|
}
|
|
|
|
void Emboss::apply_transformation(const FontProp &font_prop,
|
|
Transform3d &transformation)
|
|
{
|
|
if (font_prop.angle.has_value()) {
|
|
double angle_z = *font_prop.angle;
|
|
transformation *= Eigen::AngleAxisd(angle_z, Vec3d::UnitZ());
|
|
}
|
|
if (font_prop.distance.has_value()) {
|
|
Vec3d translate = Vec3d::UnitZ() * (*font_prop.distance);
|
|
transformation.translate(translate);
|
|
}
|
|
}
|
|
|
|
bool Emboss::is_italic(const FontFile &font, unsigned int font_index)
|
|
{
|
|
if (font_index >= font.infos.size()) return false;
|
|
std::optional<stbtt_fontinfo> font_info_opt = Private::load_font_info(font.data->data(), font_index);
|
|
|
|
if (!font_info_opt.has_value()) return false;
|
|
stbtt_fontinfo *info = &(*font_info_opt);
|
|
|
|
// https://docs.microsoft.com/cs-cz/typography/opentype/spec/name
|
|
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
|
|
// 2 ==> Style / Subfamily name
|
|
int name_id = 2;
|
|
int length;
|
|
const char* value = stbtt_GetFontNameString(info, &length,
|
|
STBTT_PLATFORM_ID_MICROSOFT,
|
|
STBTT_MS_EID_UNICODE_BMP,
|
|
STBTT_MS_LANG_ENGLISH,
|
|
name_id);
|
|
|
|
// value is big endian utf-16 i need extract only normal chars
|
|
std::string value_str;
|
|
value_str.reserve(length / 2);
|
|
for (int i = 1; i < length; i += 2)
|
|
value_str.push_back(value[i]);
|
|
|
|
// lower case
|
|
std::transform(value_str.begin(), value_str.end(), value_str.begin(),
|
|
[](unsigned char c) { return std::tolower(c); });
|
|
|
|
const std::vector<std::string> italics({"italic", "oblique"});
|
|
for (const std::string &it : italics) {
|
|
if (value_str.find(it) != std::string::npos) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::string Emboss::create_range_text(const std::string &text,
|
|
const FontFile &font,
|
|
unsigned int font_index,
|
|
bool *exist_unknown)
|
|
{
|
|
if (!Private::is_valid(font, font_index)) return {};
|
|
|
|
std::wstring ws = boost::nowide::widen(text);
|
|
|
|
// need remove symbols not contained in font
|
|
std::sort(ws.begin(), ws.end());
|
|
|
|
auto font_info_opt = Private::load_font_info(font.data->data(), 0);
|
|
if (!font_info_opt.has_value()) return {};
|
|
const stbtt_fontinfo *font_info = &(*font_info_opt);
|
|
|
|
if (exist_unknown != nullptr) *exist_unknown = false;
|
|
int prev_unicode = -1;
|
|
ws.erase(std::remove_if(ws.begin(), ws.end(),
|
|
[&prev_unicode, font_info, exist_unknown](wchar_t wc) -> bool {
|
|
int unicode = static_cast<int>(wc);
|
|
|
|
// skip white spaces
|
|
if (unicode == '\n' ||
|
|
unicode == '\r' ||
|
|
unicode == '\t') return true;
|
|
|
|
// is duplicit?
|
|
if (prev_unicode == unicode) return true;
|
|
prev_unicode = unicode;
|
|
|
|
// can find in font?
|
|
bool is_unknown = !stbtt_FindGlyphIndex(font_info, unicode);
|
|
if (is_unknown && exist_unknown != nullptr)
|
|
*exist_unknown = true;
|
|
return is_unknown;
|
|
}), ws.end());
|
|
|
|
return boost::nowide::narrow(ws);
|
|
}
|
|
|
|
double Emboss::get_shape_scale(const FontProp &fp, const FontFile &ff)
|
|
{
|
|
const auto &cn = fp.collection_number;
|
|
unsigned int font_index = (cn.has_value()) ? *cn : 0;
|
|
int unit_per_em = ff.infos[font_index].unit_per_em;
|
|
double scale = fp.size_in_mm / unit_per_em;
|
|
// Shape is scaled for store point coordinate as integer
|
|
return scale * Emboss::SHAPE_SCALE;
|
|
}
|
|
|
|
indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d,
|
|
const IProjection &projection)
|
|
{
|
|
indexed_triangle_set result;
|
|
size_t count_point = count_points(shape2d);
|
|
result.vertices.reserve(2 * count_point);
|
|
|
|
std::vector<Vec3f> &front_points = result.vertices;
|
|
std::vector<Vec3f> back_points;
|
|
back_points.reserve(count_point);
|
|
|
|
auto insert_point = [&projection, &front_points,
|
|
&back_points](const Polygon& polygon) {
|
|
for (const Point& p : polygon.points) {
|
|
auto p2 = projection.create_front_back(p);
|
|
front_points.emplace_back(p2.first);
|
|
back_points.emplace_back(p2.second);
|
|
}
|
|
};
|
|
for (const ExPolygon &expolygon : shape2d) {
|
|
insert_point(expolygon.contour);
|
|
for (const Polygon &hole : expolygon.holes) insert_point(hole);
|
|
}
|
|
// insert back points, front are already in
|
|
result.vertices.insert(result.vertices.end(),
|
|
std::make_move_iterator(back_points.begin()),
|
|
std::make_move_iterator(back_points.end()));
|
|
|
|
// CW order of triangle indices
|
|
std::vector<Vec3i> shape_triangles = Triangulation::triangulate(shape2d);
|
|
result.indices.reserve(shape_triangles.size() * 2 + count_point * 2);
|
|
// top triangles - change to CCW
|
|
for (const Vec3i &t : shape_triangles)
|
|
result.indices.emplace_back(t.x(), t.z(), t.y());
|
|
// bottom triangles - use CW
|
|
for (const Vec3i &t : shape_triangles)
|
|
result.indices.emplace_back(t.x() + count_point, t.y() + count_point,
|
|
t.z() + count_point);
|
|
|
|
// quads around - zig zag by triangles
|
|
size_t polygon_offset = 0;
|
|
auto add_quads = [&result,&polygon_offset, count_point](const Polygon& polygon) {
|
|
uint32_t polygon_points = polygon.points.size();
|
|
for (uint32_t p = 0; p < polygon_points; p++) {
|
|
uint32_t i = polygon_offset + p;
|
|
// previous index
|
|
uint32_t ip = (p == 0) ? (polygon_offset + polygon_points - 1) : (i - 1);
|
|
// bottom indices
|
|
uint32_t i2 = i + count_point;
|
|
uint32_t ip2 = ip + count_point;
|
|
|
|
result.indices.emplace_back(i, i2, ip);
|
|
result.indices.emplace_back(ip2, ip, i2);
|
|
}
|
|
polygon_offset += polygon_points;
|
|
};
|
|
|
|
for (const ExPolygon &expolygon : shape2d) {
|
|
add_quads(expolygon.contour);
|
|
for (const Polygon &hole : expolygon.holes) add_quads(hole);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::pair<Vec3f, Vec3f> Emboss::ProjectZ::create_front_back(const Point &p) const
|
|
{
|
|
Vec3f front(
|
|
static_cast<float>(p.x() * SHAPE_SCALE),
|
|
static_cast<float>(p.y() * SHAPE_SCALE),
|
|
0.f);
|
|
return std::make_pair(front, project(front));
|
|
}
|
|
|
|
Vec3f Emboss::ProjectZ::project(const Vec3f &point) const
|
|
{
|
|
Vec3f res = point; // copy
|
|
res.z() = m_depth;
|
|
return res;
|
|
}
|
|
|
|
Transform3d Emboss::create_transformation_onto_surface(const Vec3f &position,
|
|
const Vec3f &normal,
|
|
float up_limit)
|
|
{
|
|
// up and emboss direction for generated model
|
|
Vec3d text_up_dir = Vec3d::UnitY();
|
|
Vec3d text_emboss_dir = Vec3d::UnitZ();
|
|
|
|
// wanted up direction of result
|
|
Vec3d wanted_up_side = Vec3d::UnitZ();
|
|
if (std::fabs(normal.z()) > up_limit) wanted_up_side = Vec3d::UnitY();
|
|
|
|
Vec3d wanted_emboss_dir = normal.cast<double>();
|
|
// after cast from float it needs to be normalized again
|
|
wanted_emboss_dir.normalize();
|
|
|
|
// create perpendicular unit vector to surface triangle normal vector
|
|
// lay on surface of triangle and define up vector for text
|
|
Vec3d wanted_up_dir = wanted_emboss_dir
|
|
.cross(wanted_up_side)
|
|
.cross(wanted_emboss_dir);
|
|
// normal3d is NOT perpendicular to normal_up_dir
|
|
wanted_up_dir.normalize();
|
|
|
|
// perpendicular to emboss vector of text and normal
|
|
Vec3d axis_view = text_emboss_dir.cross(wanted_emboss_dir);
|
|
double angle_view = std::acos(text_emboss_dir.dot(wanted_emboss_dir)); // in rad
|
|
axis_view.normalize();
|
|
|
|
Eigen::AngleAxis view_rot(angle_view, axis_view);
|
|
Vec3d wanterd_up_rotated = view_rot.matrix().inverse() * wanted_up_dir;
|
|
wanterd_up_rotated.normalize();
|
|
double angle_up = std::acos(text_up_dir.dot(wanterd_up_rotated));
|
|
|
|
// text_view and text_view2 should have same direction
|
|
Vec3d text_view2 = text_up_dir.cross(wanterd_up_rotated);
|
|
Vec3d diff_view = text_emboss_dir - text_view2;
|
|
if (std::fabs(diff_view.x()) > 1. ||
|
|
std::fabs(diff_view.y()) > 1. ||
|
|
std::fabs(diff_view.z()) > 1.) // oposit direction
|
|
angle_up *= -1.;
|
|
|
|
Eigen::AngleAxis up_rot(angle_up, text_emboss_dir);
|
|
|
|
Transform3d transform = Transform3d::Identity();
|
|
transform.translate(position.cast<double>());
|
|
transform.rotate(view_rot);
|
|
transform.rotate(up_rot);
|
|
return transform;
|
|
}
|
|
|
|
|
|
// OrthoProject
|
|
|
|
std::pair<Vec3f, Vec3f> Emboss::OrthoProject::create_front_back(const Point &p) const {
|
|
Vec3d front(p.x(), p.y(), 0.);
|
|
Vec3f front_tr = (m_matrix * front).cast<float>();
|
|
return std::make_pair(front_tr, project(front_tr));
|
|
}
|
|
|
|
Vec3f Emboss::OrthoProject::project(const Vec3f &point) const
|
|
{
|
|
return point + m_direction;
|
|
}
|