Add initial font metrics and equation rendering.
This commit is contained in:
parent
c2027801be
commit
5ddd54dd6d
24 changed files with 868 additions and 63 deletions
|
@ -39,3 +39,53 @@ std::wstring UnicodeUtils::utf8ToUtf16WString(const std::string& input)
|
|||
throw std::logic_error("Not implemented");
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<uint32_t> UnicodeUtils::utf8ToUtf32(const std::string& input)
|
||||
{
|
||||
const auto utf_16 = utf8ToUtf16WString(input);
|
||||
|
||||
std::vector<uint32_t> output;
|
||||
std::size_t pos = 0;
|
||||
while (pos < utf_16.size())
|
||||
{
|
||||
const auto c = utf_16[pos];
|
||||
pos++;
|
||||
if (!isSurrogate(c))
|
||||
{
|
||||
output.push_back(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isHighSurrogate(c) && pos < utf_16.size() && isLowSurrogate(utf_16[pos]))
|
||||
{
|
||||
output.push_back(surrogateToUtf32(c, utf_16[pos]));
|
||||
pos++;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::logic_error("Unexpected UTF16 content given for conversion to UTF32");
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
bool UnicodeUtils::isSurrogate(wchar_t c)
|
||||
{
|
||||
return (c - 0xd800u) < 2048u;
|
||||
}
|
||||
|
||||
bool UnicodeUtils::isHighSurrogate(wchar_t c)
|
||||
{
|
||||
return (c & 0xfffffc00) == 0xd800;
|
||||
}
|
||||
|
||||
bool UnicodeUtils::isLowSurrogate(wchar_t c)
|
||||
{
|
||||
return (c & 0xfffffc00) == 0xdc00;
|
||||
}
|
||||
|
||||
uint32_t UnicodeUtils::surrogateToUtf32(wchar_t high, wchar_t low)
|
||||
{
|
||||
return (high << 10) + low - 0x35fdc00;
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class UnicodeUtils
|
||||
{
|
||||
|
@ -8,4 +9,12 @@ public:
|
|||
static std::string utf16ToUtf8String(const std::wstring& input);
|
||||
|
||||
static std::wstring utf8ToUtf16WString(const std::string& input);
|
||||
|
||||
static std::vector<uint32_t> utf8ToUtf32(const std::string& input);
|
||||
|
||||
private:
|
||||
static bool isSurrogate(wchar_t c);
|
||||
static bool isHighSurrogate(wchar_t c);
|
||||
static bool isLowSurrogate(wchar_t c);
|
||||
static uint32_t surrogateToUtf32(wchar_t high, wchar_t low);
|
||||
};
|
|
@ -0,0 +1,245 @@
|
|||
#include "LatexMathExpression.h"
|
||||
|
||||
#include "FileLogger.h"
|
||||
|
||||
LatexMathExpression::LatexMathExpression(const std::string& expression)
|
||||
: mRawBody(expression),
|
||||
mType(Type::LINEAR)
|
||||
{
|
||||
MLOG_INFO("Adding expression with content: " << expression);
|
||||
parse();
|
||||
}
|
||||
|
||||
LatexMathExpression::LatexMathExpression(Type type)
|
||||
: mType(type)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
LatexMathExpression::~LatexMathExpression()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void LatexMathExpression::onTagBodyChar(char c)
|
||||
{
|
||||
if (c == '}')
|
||||
{
|
||||
onRBrace();
|
||||
}
|
||||
else
|
||||
{
|
||||
mWorkingString += c;
|
||||
}
|
||||
}
|
||||
|
||||
void LatexMathExpression::onFreeChar(char c)
|
||||
{
|
||||
if (c == '\\')
|
||||
{
|
||||
mLineState = LineState::IN_TAG_NAME;
|
||||
}
|
||||
else if (c == '{')
|
||||
{
|
||||
onLBrace();
|
||||
}
|
||||
else if (c == '}')
|
||||
{
|
||||
onRBrace();
|
||||
}
|
||||
else if (c == '^')
|
||||
{
|
||||
onSuperscript();
|
||||
}
|
||||
else if (c == '_')
|
||||
{
|
||||
onSubscript();
|
||||
}
|
||||
else if (std::isblank(c))
|
||||
{
|
||||
onSpace();
|
||||
}
|
||||
else
|
||||
{
|
||||
mWorkingString += c;
|
||||
}
|
||||
}
|
||||
|
||||
void LatexMathExpression::parse()
|
||||
{
|
||||
mLineState = LineState::NONE;
|
||||
mWorkingString.clear();
|
||||
|
||||
for (auto c : mRawBody)
|
||||
{
|
||||
if (mLineState == LineState::IN_TAG_BODY)
|
||||
{
|
||||
onTagBodyChar(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
onFreeChar(c);
|
||||
}
|
||||
}
|
||||
onSpace();
|
||||
onCloseExpression();
|
||||
}
|
||||
|
||||
void LatexMathExpression::onSpace()
|
||||
{
|
||||
if (mLineState == LineState::IN_TAG_NAME)
|
||||
{
|
||||
onFinishedTagName();
|
||||
}
|
||||
else
|
||||
{
|
||||
onLiteral();
|
||||
}
|
||||
mWorkingString.clear();
|
||||
}
|
||||
|
||||
void LatexMathExpression::onLBrace()
|
||||
{
|
||||
if (mLineState == LineState::IN_TAG_NAME)
|
||||
{
|
||||
onFinishedTagName();
|
||||
}
|
||||
else if (mLineState == LineState::AWAITING_LBRACE)
|
||||
{
|
||||
mLineState = LineState::IN_TAG_BODY;
|
||||
}
|
||||
else if (mLineState == LineState::IN_TAG_BODY)
|
||||
{
|
||||
mOpenBraceCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
mWorkingString += "{";
|
||||
}
|
||||
}
|
||||
|
||||
void LatexMathExpression::onRBrace()
|
||||
{
|
||||
if (mLineState == LineState::IN_TAG_BODY)
|
||||
{
|
||||
if (mOpenBraceCount == 0)
|
||||
{
|
||||
onTagBody();
|
||||
}
|
||||
else
|
||||
{
|
||||
mOpenBraceCount--;
|
||||
mWorkingString += "}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mWorkingString += "}";
|
||||
}
|
||||
}
|
||||
|
||||
void LatexMathExpression::setRawContent(const std::string& content)
|
||||
{
|
||||
auto expression = std::make_unique<LatexMathExpression>(content);
|
||||
mExpressions.push_back(std::move(expression));
|
||||
}
|
||||
|
||||
void LatexMathExpression::onTagBody()
|
||||
{
|
||||
onCloseExpression();
|
||||
}
|
||||
|
||||
void LatexMathExpression::onSuperscript()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void LatexMathExpression::onSubscript()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void LatexMathExpression::onLiteral()
|
||||
{
|
||||
LatexMathSymbol symbol{ LatexMathSymbol::Type::RAW, mWorkingString, mWorkingString };
|
||||
mWorkingSymbols.push_back(symbol);
|
||||
}
|
||||
|
||||
void LatexMathExpression::onFinishedTagName()
|
||||
{
|
||||
if (auto lookup = LatexSymbolLookup::getKnownTag(mWorkingString); lookup)
|
||||
{
|
||||
if (mLineState == LineState::AWAITING_LBRACE || mLineState == LineState::IN_TAG_NAME)
|
||||
{
|
||||
onCloseExpression();
|
||||
|
||||
if (*lookup == LatexMathSymbol::Tag::FRAC)
|
||||
{
|
||||
mWorkingType = Type::FRACTION;
|
||||
auto expression = std::make_unique<LatexMathExpression>(mWorkingType);
|
||||
mWorkingExpression = expression.get();
|
||||
mExpressions.push_back(std::move(expression));
|
||||
mWorkingString.clear();
|
||||
}
|
||||
|
||||
mLineState = LineState::IN_TAG_BODY;
|
||||
}
|
||||
}
|
||||
else if (auto lookup = LatexSymbolLookup::getSymbolUtf8(mWorkingString); lookup)
|
||||
{
|
||||
LatexMathSymbol symbol{ LatexMathSymbol::Type::TAG, mWorkingString, *lookup };
|
||||
mWorkingSymbols.push_back(symbol);
|
||||
mLineState = LineState::NONE;
|
||||
}
|
||||
}
|
||||
|
||||
void LatexMathExpression::onCloseExpression()
|
||||
{
|
||||
if (mWorkingType == Type::LEAF)
|
||||
{
|
||||
if (!mWorkingSymbols.empty())
|
||||
{
|
||||
auto expression = std::make_unique<LatexMathExpression>(mWorkingType);
|
||||
expression->setContent(mWorkingSymbols);
|
||||
mExpressions.push_back(std::move(expression));
|
||||
}
|
||||
}
|
||||
else if (mWorkingType == Type::FRACTION)
|
||||
{
|
||||
mWorkingExpression->setRawContent(mWorkingString);
|
||||
|
||||
if (mWorkingExpression->getExpressions().size() == 2)
|
||||
{
|
||||
mWorkingType = Type::LEAF;
|
||||
mLineState = LineState::NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
mLineState = LineState::AWAITING_LBRACE;
|
||||
mOpenBraceCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
mWorkingString.clear();
|
||||
mWorkingSymbols.clear();
|
||||
}
|
||||
|
||||
void LatexMathExpression::setContent(std::vector<LatexMathSymbol>& symbols)
|
||||
{
|
||||
mSymbols = symbols;
|
||||
}
|
||||
|
||||
const std::vector<LatexMathSymbol>& LatexMathExpression::getSymbols() const
|
||||
{
|
||||
return mSymbols;
|
||||
}
|
||||
|
||||
const LatexMathExpression::Type LatexMathExpression::getType() const
|
||||
{
|
||||
return mType;
|
||||
}
|
||||
|
||||
const std::vector<LatexMathExpressionPtr>& LatexMathExpression::getExpressions() const
|
||||
{
|
||||
return mExpressions;
|
||||
}
|
|
@ -6,36 +6,81 @@
|
|||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class LatexMathExpression;
|
||||
using LatexMathExpressionPtr = std::unique_ptr<LatexMathExpression>;
|
||||
|
||||
class LatexMathExpression
|
||||
{
|
||||
public:
|
||||
LatexMathExpression(const std::string& expression = {})
|
||||
: mRawExpression(expression)
|
||||
enum class Type
|
||||
{
|
||||
LINEAR,
|
||||
LEAF,
|
||||
FRACTION,
|
||||
SUBSCRIPT,
|
||||
SUPERSCRIPT
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void parse()
|
||||
enum class LineState
|
||||
{
|
||||
for (auto c : mRawExpression)
|
||||
{
|
||||
NONE,
|
||||
IN_TAG_NAME,
|
||||
AWAITING_LBRACE,
|
||||
IN_TAG_BODY
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
LatexMathExpression(const std::string& expression);
|
||||
|
||||
const std::vector<LatexMathSymbol>& getSymbols() const
|
||||
{
|
||||
return mSymbols;
|
||||
}
|
||||
LatexMathExpression(Type type);
|
||||
|
||||
~LatexMathExpression();
|
||||
|
||||
const std::vector<LatexMathSymbol>& getSymbols() const;
|
||||
|
||||
const std::vector<LatexMathExpressionPtr>& getExpressions() const;
|
||||
|
||||
const Type getType() const;
|
||||
|
||||
void setContent(std::vector<LatexMathSymbol>& symbols);
|
||||
|
||||
void setRawContent(const std::string& content);
|
||||
|
||||
void onTagBodyChar(char c);
|
||||
|
||||
void onFreeChar(char c);
|
||||
|
||||
void onSpace();
|
||||
|
||||
void onLBrace();
|
||||
|
||||
void onRBrace();
|
||||
|
||||
void onSuperscript();
|
||||
|
||||
void onSubscript();
|
||||
|
||||
void onLiteral();
|
||||
|
||||
void onTagBody();
|
||||
|
||||
void onFinishedTagName();
|
||||
|
||||
void onCloseExpression();
|
||||
|
||||
void parse();
|
||||
|
||||
private:
|
||||
std::size_t mOpenBraceCount{ 0 };
|
||||
|
||||
unsigned mOpenTagCount{ 0 };
|
||||
LineState mLineState{ LineState::NONE };
|
||||
std::string mWorkingString;
|
||||
std::string mRawBody;
|
||||
|
||||
std::string mRawExpression;
|
||||
std::unique_ptr<LatexMathExpression> mSuperScriptExpr;
|
||||
std::unique_ptr<LatexMathExpression> mSubScriptExpr;
|
||||
std::unique_ptr<LatexMathExpression> mEnclosedExpr;
|
||||
Type mWorkingType{ Type::LEAF };
|
||||
LatexMathExpression* mWorkingExpression{ nullptr };
|
||||
Type mType{ Type::LEAF };
|
||||
std::vector<LatexMathSymbol> mWorkingSymbols;
|
||||
|
||||
std::vector<LatexMathSymbol> mSymbols;
|
||||
std::vector<LatexMathExpressionPtr> mExpressions;
|
||||
};
|
|
@ -10,6 +10,11 @@ std::unordered_map<std::string, std::wstring> LatexSymbolLookup::mSymbols = {
|
|||
{"psi", L"\u03C8"}
|
||||
};
|
||||
|
||||
|
||||
std::unordered_map<std::string, LatexMathSymbol::Tag> LatexSymbolLookup::mTags = {
|
||||
{"frac", LatexMathSymbol::Tag::FRAC},
|
||||
};
|
||||
|
||||
std::optional<std::string> LatexSymbolLookup::getSymbolUtf8(const std::string& tag)
|
||||
{
|
||||
if (auto entry = getSymbolUtf16(tag); entry)
|
||||
|
@ -33,3 +38,15 @@ std::optional<std::wstring> LatexSymbolLookup::getSymbolUtf16(const std::string&
|
|||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<LatexMathSymbol::Tag> LatexSymbolLookup::getKnownTag(const std::string& tag)
|
||||
{
|
||||
if (auto iter = mTags.find(tag); iter != mTags.end())
|
||||
{
|
||||
return iter->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
|
@ -15,8 +15,24 @@ struct LatexMathSymbol
|
|||
ENCLOSING,
|
||||
ENCLOSING_TAG
|
||||
};
|
||||
|
||||
enum class Tag
|
||||
{
|
||||
NONE,
|
||||
FRAC
|
||||
};
|
||||
|
||||
LatexMathSymbol(Type type, const std::string& name, const std::string& unicode)
|
||||
: mType(type),
|
||||
mName(name),
|
||||
mUnicode(unicode)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Type mType{ Type::RAW };
|
||||
std::string mName;
|
||||
std::wstring mUnicode;
|
||||
std::string mUnicode;
|
||||
};
|
||||
|
||||
class LatexSymbolLookup
|
||||
|
@ -26,6 +42,21 @@ public:
|
|||
|
||||
static std::optional<std::wstring> getSymbolUtf16(const std::string& tag);
|
||||
|
||||
static std::optional<LatexMathSymbol::Tag> getKnownTag(const std::string& tag);
|
||||
|
||||
static std::size_t getNumTagBodies(LatexMathSymbol::Tag tag)
|
||||
{
|
||||
switch (tag)
|
||||
{
|
||||
case LatexMathSymbol::Tag::FRAC:
|
||||
return 2;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static std::unordered_map<std::string, std::wstring> mSymbols;
|
||||
|
||||
static std::unordered_map<std::string, LatexMathSymbol::Tag> mTags;
|
||||
};
|
|
@ -1,7 +1,83 @@
|
|||
#include "EquationNode.h"
|
||||
|
||||
#include "LatexMathExpression.h"
|
||||
#include "TextNode.h"
|
||||
|
||||
#include "FileLogger.h"
|
||||
|
||||
EquationNode::EquationNode(const Transform& t)
|
||||
: AbstractVisualNode(t)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
EquationNode::~EquationNode()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void EquationNode::setContent(LatexMathExpression* content)
|
||||
{
|
||||
mContent = content;
|
||||
mContentDirty = true;
|
||||
}
|
||||
|
||||
void EquationNode::update(SceneInfo* sceneInfo)
|
||||
{
|
||||
if (mContentDirty)
|
||||
{
|
||||
createOrUpdateGeometry(sceneInfo);
|
||||
mContentDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void EquationNode::addExpression(const LatexMathExpression* expression)
|
||||
{
|
||||
if (expression->getType() == LatexMathExpression::Type::LINEAR)
|
||||
{
|
||||
MLOG_INFO("Processing linear expr with : " << expression->getExpressions().size() << " children");
|
||||
for (const auto& expression : expression->getExpressions())
|
||||
{
|
||||
addExpression(expression.get());
|
||||
}
|
||||
}
|
||||
else if(expression->getType() == LatexMathExpression::Type::LEAF)
|
||||
{
|
||||
std::string content = "_";
|
||||
for (const auto& symbol : expression->getSymbols())
|
||||
{
|
||||
content += symbol.mUnicode;
|
||||
}
|
||||
MLOG_INFO("Processing leaf expr with content: " << content);
|
||||
auto node = std::make_unique<TextNode>(content, Transform(mTextCursor));
|
||||
node->setWidth(100);
|
||||
node->setFont(mDefaultFont);
|
||||
addChild(node.get());
|
||||
mText.push_back(std::move(node));
|
||||
|
||||
mTextCursor.move(20.0, 0.0);
|
||||
}
|
||||
else if (expression->getType() == LatexMathExpression::Type::FRACTION)
|
||||
{
|
||||
MLOG_INFO("Processing frac expr");
|
||||
if (expression->getExpressions().size() == 2)
|
||||
{
|
||||
auto x_cache = mTextCursor.getX();
|
||||
mTextCursor.move(0.0, -10.0);
|
||||
addExpression(expression->getExpressions()[0].get());
|
||||
|
||||
mTextCursor.move(mTextCursor.getX() - x_cache, 20.0);
|
||||
addExpression(expression->getExpressions()[1].get());
|
||||
|
||||
mTextCursor.move(0.0, -10.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EquationNode::createOrUpdateGeometry(SceneInfo* sceneInfo)
|
||||
{
|
||||
mChildren.clear();
|
||||
mText.clear();
|
||||
|
||||
addExpression(mContent);
|
||||
}
|
|
@ -1,9 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include "AbstractVisualNode.h"
|
||||
#include "FontItem.h"
|
||||
|
||||
class TextNode;
|
||||
class LatexMathExpression;
|
||||
|
||||
class EquationNode : public AbstractVisualNode
|
||||
{
|
||||
public:
|
||||
EquationNode(const Transform& t = {});
|
||||
|
||||
virtual ~EquationNode();
|
||||
|
||||
void setContent(LatexMathExpression* content);
|
||||
|
||||
void update(SceneInfo* sceneInfo) override;
|
||||
|
||||
protected:
|
||||
void createOrUpdateGeometry(SceneInfo* sceneInfo);
|
||||
|
||||
void addExpression(const LatexMathExpression* expression);
|
||||
|
||||
|
||||
std::vector<std::unique_ptr<TextNode> > mText;
|
||||
|
||||
FontItem mDefaultFont{ "Cambria Math", 14 };
|
||||
//FontItem mDefaultFont{ "Latin Modern Math", 14 };
|
||||
|
||||
Point mTextCursor;
|
||||
|
||||
LatexMathExpression* mContent{ nullptr };
|
||||
bool mContentDirty{ true };
|
||||
};
|
|
@ -12,4 +12,9 @@ class BasicFontEngine : public IFontEngine
|
|||
virtual void loadFontFace(const std::filesystem::path&, float){};
|
||||
|
||||
virtual std::unique_ptr<FontGlyph> loadGlyph(unsigned){return nullptr;};
|
||||
|
||||
double getHorizontalAdvance(const FontItem& fontItem, const std::string& content) override
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,20 +1,29 @@
|
|||
#include "FontsManager.h"
|
||||
|
||||
#ifdef HAS_FREETYPE
|
||||
#include "FreeTypeFontEngine.h"
|
||||
#ifdef _WIN32
|
||||
#include "DirectWriteFontEngine.h"
|
||||
#include "DirectWriteHelpers.h"
|
||||
#else
|
||||
#include "BasicFontEngine.h"
|
||||
#ifdef HAS_FREETYPE
|
||||
#include "FreeTypeFontEngine.h"
|
||||
#else
|
||||
#include "BasicFontEngine.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "FontGlyph.h"
|
||||
|
||||
FontsManager::FontsManager()
|
||||
{
|
||||
#ifdef HAS_FREETYPE
|
||||
mFontEngine = std::make_unique<FreeTypeFontEngine>();
|
||||
mUsesGlyphs = true;
|
||||
#ifdef _WIN32
|
||||
mFontEngine = std::make_unique<DirectWriteFontEngine>();
|
||||
#else
|
||||
mFontEngine = std::make_unique<BasicFontEngine>();
|
||||
#ifdef HAS_FREETYPE
|
||||
mFontEngine = std::make_unique<FreeTypeFontEngine>();
|
||||
mUsesGlyphs = true;
|
||||
#else
|
||||
mFontEngine = std::make_unique<BasicFontEngine>();
|
||||
#endif
|
||||
#endif
|
||||
mFontEngine->initialize();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "FontItem.h"
|
||||
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
|
||||
|
@ -16,4 +18,6 @@ public:
|
|||
virtual void loadFontFace(const std::filesystem::path& fontFile, float penSize = 16) = 0;
|
||||
|
||||
virtual std::unique_ptr<FontGlyph> loadGlyph(uint32_t charCode) = 0;
|
||||
|
||||
virtual double getHorizontalAdvance(const FontItem& fontItem, const std::string& content) = 0;
|
||||
};
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
#include "UnicodeUtils.h"
|
||||
|
||||
#include "DirectWriteHelpers.h"
|
||||
#include "FileLogger.h"
|
||||
|
||||
#include <dwrite.h>
|
||||
#include <cmath>
|
||||
|
||||
void DirectWriteFontEngine::initialize()
|
||||
{
|
||||
|
@ -35,6 +37,83 @@ std::unique_ptr<FontGlyph> DirectWriteFontEngine::loadGlyph(uint32_t charCode)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
IDWriteFontFamily* DirectWriteFontEngine::getSystemFontFamily(const std::string& fontName)
|
||||
{
|
||||
if (auto iter = mSystemFontFamilies.find(fontName); iter != mSystemFontFamilies.end())
|
||||
{
|
||||
return iter->second.Get();
|
||||
}
|
||||
else
|
||||
{
|
||||
loadSystemFontFamily(fontName);
|
||||
if (auto iter = mSystemFontFamilies.find(fontName); iter != mSystemFontFamilies.end())
|
||||
{
|
||||
return iter->second.Get();
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DirectWriteFontEngine::loadSystemFontFamily(const std::string& fontName)
|
||||
{
|
||||
Microsoft::WRL::ComPtr<IDWriteFontCollection> system_collection;
|
||||
if (SUCCEEDED(mDWriteFactory->GetSystemFontCollection(&system_collection, FALSE)))
|
||||
{
|
||||
UINT32 index{ 0 };
|
||||
BOOL exists{ false };
|
||||
system_collection->FindFamilyName(UnicodeUtils::utf8ToUtf16WString(fontName).c_str(), &index, &exists);
|
||||
if (exists)
|
||||
{
|
||||
Microsoft::WRL::ComPtr<IDWriteFontFamily> font_family;
|
||||
if (SUCCEEDED(system_collection->GetFontFamily(index, &font_family)))
|
||||
{
|
||||
mSystemFontFamilies[fontName] = font_family;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
IDWriteFontFace* DirectWriteFontEngine::getDefaultFontFace(const std::string& fontName)
|
||||
{
|
||||
if (auto iter = mDefaultFontFaces.find(fontName); iter != mDefaultFontFaces.end())
|
||||
{
|
||||
return iter->second.Get();
|
||||
}
|
||||
else
|
||||
{
|
||||
loadDefaultFontFace(fontName);
|
||||
if (auto iter = mDefaultFontFaces.find(fontName); iter != mDefaultFontFaces.end())
|
||||
{
|
||||
return iter->second.Get();
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DirectWriteFontEngine::loadDefaultFontFace(const std::string& fontName)
|
||||
{
|
||||
if (auto family = getSystemFontFamily(fontName); family)
|
||||
{
|
||||
Microsoft::WRL::ComPtr<IDWriteFont> font;
|
||||
if (SUCCEEDED(family->GetFirstMatchingFont(DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, &font)))
|
||||
{
|
||||
Microsoft::WRL::ComPtr<IDWriteFontFace> font_face;
|
||||
font->CreateFontFace(&font_face);
|
||||
mDefaultFontFaces[fontName] = font_face;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<GlyphRunOutlines> DirectWriteFontEngine::getGlyphRunOutlines(const std::vector<uint32_t>& charCodes)
|
||||
{
|
||||
initializeOffScreenRenderer();
|
||||
|
@ -52,3 +131,42 @@ std::unique_ptr<GlyphRunOutlines> DirectWriteFontEngine::getGlyphRunOutlines(con
|
|||
return mOffScreenRenderer->getGlypyRunOutline();
|
||||
}
|
||||
|
||||
double DirectWriteFontEngine::getHorizontalAdvance(const FontItem& fontItem, const std::string& content)
|
||||
{
|
||||
auto font_face = getDefaultFontFace(fontItem.getFaceName());
|
||||
if (!font_face)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
DWRITE_FONT_METRICS font_metrics;
|
||||
font_face->GetMetrics(&font_metrics);
|
||||
|
||||
const auto design_per_em = font_metrics.designUnitsPerEm;
|
||||
MLOG_INFO("Design units: " << design_per_em);
|
||||
|
||||
const auto utf32_content = UnicodeUtils::utf8ToUtf32(content);
|
||||
|
||||
std::vector<UINT16> glyph_indices(utf32_content.size());
|
||||
if (SUCCEEDED(font_face->GetGlyphIndices(utf32_content.data(), static_cast<UINT32>(utf32_content.size()), &glyph_indices[0])))
|
||||
{
|
||||
std::vector<DWRITE_GLYPH_METRICS> metrics(utf32_content.size());
|
||||
double total_advance{ 0 };
|
||||
if (SUCCEEDED(font_face->GetDesignGlyphMetrics(glyph_indices.data(), static_cast<UINT32>(glyph_indices.size()), &metrics[0], false)))
|
||||
{
|
||||
for (std::size_t idx = 0; idx < utf32_content.size(); idx++)
|
||||
{
|
||||
const auto design_width = metrics[idx].advanceWidth;
|
||||
// https://learn.microsoft.com/en-us/windows/win32/gdi/device-vs--design-units
|
||||
double device_width = (design_width / static_cast<double>(design_per_em)) * (fontItem.getSize() / 72.0) * 96.0 * 0.75;
|
||||
|
||||
MLOG_INFO("Got advance width: " << device_width);
|
||||
//total_advance += std::floor(device_width);
|
||||
total_advance += device_width;
|
||||
}
|
||||
return std::round(total_advance);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
#include "IFontEngine.h"
|
||||
#include "FontGlyph.h"
|
||||
#include "FontItem.h"
|
||||
|
||||
#include <wrl.h>
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
class GlyphOutlineGeometrySink;
|
||||
class OffScreenTextRenderer;
|
||||
|
@ -13,6 +15,8 @@ class OffScreenTextRenderer;
|
|||
struct IDWriteFactory;
|
||||
struct IDWriteTextFormat;
|
||||
struct IDWriteTextLayout;
|
||||
struct IDWriteFontFace;
|
||||
struct IDWriteFontFamily;
|
||||
|
||||
class DirectWriteFontEngine : public IFontEngine
|
||||
{
|
||||
|
@ -27,10 +31,24 @@ public:
|
|||
|
||||
std::unique_ptr<GlyphRunOutlines> getGlyphRunOutlines(const std::vector<uint32_t>& charCodes);
|
||||
|
||||
IDWriteFontFamily* getSystemFontFamily(const std::string& fontName);
|
||||
|
||||
bool loadSystemFontFamily(const std::string& fontName);
|
||||
|
||||
IDWriteFontFace* getDefaultFontFace(const std::string& fontName);
|
||||
|
||||
bool loadDefaultFontFace(const std::string& fontName);
|
||||
|
||||
double getHorizontalAdvance(const FontItem& fontItem, const std::string& content) override;
|
||||
|
||||
private:
|
||||
void initializeOffScreenRenderer();
|
||||
|
||||
bool mIsValid{ false };
|
||||
|
||||
std::unordered_map<std::string, Microsoft::WRL::ComPtr<IDWriteFontFace> > mDefaultFontFaces;
|
||||
std::unordered_map<std::string, Microsoft::WRL::ComPtr<IDWriteFontFamily> > mSystemFontFamilies;
|
||||
|
||||
Microsoft::WRL::ComPtr<IDWriteFactory> mDWriteFactory;
|
||||
Microsoft::WRL::ComPtr<IDWriteTextFormat> mTextFormat;
|
||||
Microsoft::WRL::ComPtr<IDWriteTextLayout> mTextLayout;
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#include "SceneInfo.h"
|
||||
|
||||
#include "SceneText.h"
|
||||
#include "SceneModel.h"
|
||||
#include "Rectangle.h"
|
||||
|
||||
#include "Color.h"
|
||||
|
||||
|
@ -18,6 +20,8 @@ TextNode::TextNode(const std::string& content, const Transform& transform)
|
|||
: MaterialNode(transform)
|
||||
{
|
||||
mTextData.mContent= content;
|
||||
setFillColor(Color(0, 0, 0));
|
||||
setHasStrokeColor(false);
|
||||
}
|
||||
|
||||
TextNode::~TextNode()
|
||||
|
@ -92,15 +96,39 @@ SceneItem* TextNode::getSceneItem(std::size_t idx) const
|
|||
{
|
||||
return mTextItem.get();
|
||||
}
|
||||
else
|
||||
else if (idx == 1)
|
||||
{
|
||||
return 0;
|
||||
if (mNodeBounds)
|
||||
{
|
||||
return mNodeBounds.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
return mTextBounds.get();
|
||||
}
|
||||
}
|
||||
else if (idx == 2)
|
||||
{
|
||||
if (mTextBounds)
|
||||
{
|
||||
return mTextBounds.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::size_t TextNode::getNumSceneItems() const
|
||||
{
|
||||
return 1;
|
||||
auto count = 1;
|
||||
if (mNodeBounds)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
if (mTextBounds)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void TextNode::updateLines(FontsManager* fontsManager)
|
||||
|
@ -145,12 +173,58 @@ void TextNode::updateLines(FontsManager* fontsManager)
|
|||
}
|
||||
}
|
||||
|
||||
void TextNode::setRenderNodeBounds(bool render)
|
||||
{
|
||||
mRenderNodeBounds = render;
|
||||
}
|
||||
|
||||
void TextNode::setRenderTextBounds(bool render)
|
||||
{
|
||||
mRenderTextBounds = render;
|
||||
}
|
||||
|
||||
void TextNode::update(SceneInfo* sceneInfo)
|
||||
{
|
||||
if (!mTextItem)
|
||||
{
|
||||
mTextItem = std::make_unique<SceneText>();
|
||||
mTextItem->setName(mName + "_SceneText");
|
||||
|
||||
mContentWidth = sceneInfo->mFontsManager->getFontEngine()->getHorizontalAdvance(mTextData.mFont, mTextData.mContent);
|
||||
mContentHeight = mTextData.mFont.getSize();
|
||||
|
||||
if (mWidth == 1.0 && mHeight == 1.0)
|
||||
{
|
||||
mTextItem->setTextWidth(mContentWidth);
|
||||
mTextItem->setTextHeight(mContentHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
mTextItem->setTextWidth(mWidth);
|
||||
mTextItem->setTextHeight(mHeight);
|
||||
}
|
||||
|
||||
if (mRenderNodeBounds)
|
||||
{
|
||||
auto rect = std::make_unique<ntk::Rectangle>(Point(0.0, 0.0), mWidth, mHeight);
|
||||
mNodeBounds = std::make_unique<SceneModel>();
|
||||
mNodeBounds->updateGeometry(std::move(rect));
|
||||
|
||||
BasicMaterial bounds_material;
|
||||
bounds_material.setStrokeColor(Color(255, 0, 0));
|
||||
mNodeBounds->updateSolidMaterial(bounds_material);
|
||||
}
|
||||
|
||||
if (mRenderTextBounds)
|
||||
{
|
||||
auto rect = std::make_unique<ntk::Rectangle>(Point(0.0, 0.0), mContentWidth, mContentHeight);
|
||||
mTextBounds = std::make_unique<SceneModel>();
|
||||
mTextBounds->updateGeometry(std::move(rect));
|
||||
|
||||
BasicMaterial bounds_material;
|
||||
bounds_material.setStrokeColor(Color(0, 255, 0));
|
||||
mTextBounds->updateSolidMaterial(bounds_material);
|
||||
}
|
||||
}
|
||||
|
||||
if (mTransformIsDirty || mContentIsDirty)
|
||||
|
@ -167,9 +241,16 @@ void TextNode::update(SceneInfo* sceneInfo)
|
|||
|
||||
if (mTransformIsDirty)
|
||||
{
|
||||
//mTextItem->updateTransform({mLocation});
|
||||
mTextItem->setTextWidth(mWidth);
|
||||
mTextItem->setTextHeight(mHeight);
|
||||
if (mWidth == 1.0 && mHeight == 1.0)
|
||||
{
|
||||
mTextItem->setTextWidth(mContentWidth);
|
||||
mTextItem->setTextHeight(mContentHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
mTextItem->setTextWidth(mWidth);
|
||||
mTextItem->setTextHeight(mHeight);
|
||||
}
|
||||
mTransformIsDirty = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <string>
|
||||
|
||||
class FontsManager;
|
||||
class SceneModel;
|
||||
|
||||
class TextNode : public MaterialNode
|
||||
{
|
||||
|
@ -34,9 +35,11 @@ public:
|
|||
void setHeight(double height);
|
||||
|
||||
void setContent(const std::string& content);
|
||||
|
||||
void setFont(const FontItem& font);
|
||||
|
||||
void setRenderNodeBounds(bool render);
|
||||
void setRenderTextBounds(bool render);
|
||||
|
||||
void update(SceneInfo* sceneInfo) override;
|
||||
private:
|
||||
|
||||
|
@ -49,7 +52,15 @@ private:
|
|||
double mWidth{1};
|
||||
double mHeight{1};
|
||||
|
||||
double mContentWidth{ 1 };
|
||||
double mContentHeight{ 1 };
|
||||
|
||||
std::unique_ptr<SceneText> mTextItem;
|
||||
|
||||
bool mRenderNodeBounds{ false };
|
||||
bool mRenderTextBounds{ false };
|
||||
std::unique_ptr<SceneModel> mNodeBounds;
|
||||
std::unique_ptr<SceneModel> mTextBounds;
|
||||
};
|
||||
|
||||
using TextNodetr = std::unique_ptr<TextNode>;
|
||||
|
|
|
@ -235,11 +235,9 @@ void SvgPainter::paintText(SvgDocument* document, SceneText* text) const
|
|||
{
|
||||
auto svg_text = std::make_unique<SvgTextElement>();
|
||||
svg_text->setContent(text->getTextData().mContent);
|
||||
auto loc = text->getTransform().getLocation();
|
||||
|
||||
loc.move(text->getTextWidth() / 2.0, text->getTextHeight()/2.0);
|
||||
svg_text->setLocation(loc);
|
||||
|
||||
Point centre(text->getTextWidth() / 2.0, text->getTextHeight() / 2.0);
|
||||
svg_text->setLocation(centre);
|
||||
svg_text->setFontFamily(text->getTextData().mFont.getFaceName());
|
||||
|
||||
svg_text->setFill(text->getSolidMaterial().getFillColor());
|
||||
|
@ -248,9 +246,14 @@ void SvgPainter::paintText(SvgDocument* document, SceneText* text) const
|
|||
{
|
||||
svg_text->setFillOpacity(opacity);
|
||||
}
|
||||
|
||||
svg_text->setFontSize(text->getTextData().mFont.getSize());
|
||||
|
||||
const auto transform = text->getTransform();
|
||||
if (!transform.isDefaultTransform())
|
||||
{
|
||||
svg_text->addAttribute(toTransform(transform));
|
||||
}
|
||||
|
||||
document->getRoot()->addChild(std::move(svg_text));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
add_subdirectory(test_utils)
|
||||
|
||||
add_subdirectory(fonts)
|
||||
add_subdirectory(geometry)
|
||||
add_subdirectory(graphics)
|
||||
add_subdirectory(publishing)
|
||||
|
@ -13,7 +14,6 @@ set(TEST_MODULES
|
|||
compression
|
||||
core
|
||||
database
|
||||
fonts
|
||||
image
|
||||
ipc
|
||||
network
|
||||
|
|
|
@ -4,23 +4,24 @@ if(UNIX)
|
|||
find_package(Freetype QUIET)
|
||||
if(Freetype_FOUND)
|
||||
set(PLATFORM_UNIT_TEST_FILES
|
||||
fonts/TestFreeTypeFontEngine.cpp
|
||||
TestFreeTypeFontEngine.cpp
|
||||
)
|
||||
endif()
|
||||
else()
|
||||
set(PLATFORM_UNIT_TEST_FILES
|
||||
fonts/TestDirectWriteFontEngine.cpp
|
||||
TestDirectWriteFontEngine.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
set(MODULE_NAME fonts)
|
||||
|
||||
set(FONTS_UNIT_TEST_FILES
|
||||
fonts/TestFontReader.cpp
|
||||
${PLATFORM_UNIT_TEST_FILES}
|
||||
PARENT_SCOPE
|
||||
)
|
||||
list(APPEND UNIT_TEST_FILES
|
||||
TestFontReader.cpp
|
||||
${PLATFORM_UNIT_TEST_FILES}
|
||||
)
|
||||
|
||||
set(FONTS_UNIT_TEST_DEPENDENCIES
|
||||
fonts
|
||||
PARENT_SCOPE
|
||||
)
|
||||
set(UNIT_TEST_TARGET_NAME ${MODULE_NAME}_unit_tests)
|
||||
|
||||
add_executable(${UNIT_TEST_TARGET_NAME} ${CMAKE_SOURCE_DIR}/test/test_runner.cpp ${UNIT_TEST_FILES})
|
||||
target_link_libraries(${UNIT_TEST_TARGET_NAME} PUBLIC test_utils fonts)
|
||||
set_property(TARGET ${UNIT_TEST_TARGET_NAME} PROPERTY FOLDER test/${MODULE_NAME})
|
|
@ -9,6 +9,7 @@
|
|||
#include "SvgWriter.h"
|
||||
#include "SvgShapeElements.h"
|
||||
#include "File.h"
|
||||
#include "FontItem.h"
|
||||
|
||||
TEST_CASE(TestDirectWriteFontEngine, "fonts")
|
||||
{
|
||||
|
@ -34,3 +35,16 @@ TEST_CASE(TestDirectWriteFontEngine, "fonts")
|
|||
file.writeText(doc_string);
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE(TestDirectWriteFontEngine_GlyphMetrics, "fonts")
|
||||
{
|
||||
DirectWriteFontEngine font_engine;
|
||||
font_engine.initialize();
|
||||
|
||||
FontItem font("Verdana", 16);
|
||||
|
||||
const auto advance = font_engine.getHorizontalAdvance(font, "abc");
|
||||
MLOG_INFO("Advance is: " << advance);
|
||||
|
||||
REQUIRE(advance != 0.0);
|
||||
}
|
|
@ -23,6 +23,7 @@ endif()
|
|||
|
||||
set(UNIT_TEST_FILES
|
||||
TestRasterizer.cpp
|
||||
TestTextRendering.cpp
|
||||
${PLATFORM_UNIT_TEST_FILES}
|
||||
)
|
||||
|
||||
|
|
22
test/graphics/TestTextRendering.cpp
Normal file
22
test/graphics/TestTextRendering.cpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#include "TestFramework.h"
|
||||
#include "TestUtils.h"
|
||||
#include "TestRenderUtils.h"
|
||||
|
||||
#include "TextNode.h"
|
||||
|
||||
TEST_CASE(TestTextRendeing_WithBoundingBoxes, "graphics")
|
||||
{
|
||||
TestRenderer renderer(800, 800);
|
||||
|
||||
auto loc = Point(10, 10);
|
||||
|
||||
TextNode text_node("abcdefgh", Transform(loc));
|
||||
text_node.setRenderNodeBounds(true);
|
||||
text_node.setRenderTextBounds(true);
|
||||
|
||||
auto scene = renderer.getScene();
|
||||
scene->addNode(&text_node);
|
||||
|
||||
renderer.writeSvg(TestUtils::getTestOutputDir(__FILE__) / "text_with_bounding_box.svg");
|
||||
renderer.write(TestUtils::getTestOutputDir(__FILE__) / "text_with_bounding_box.png");
|
||||
};
|
|
@ -4,13 +4,16 @@
|
|||
#include "TestRenderUtils.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include "FontItem.h"
|
||||
#include "TextNode.h"
|
||||
#include "LatexSymbols.h"
|
||||
#include "EquationNode.h"
|
||||
#include "LatexMathExpression.h"
|
||||
|
||||
|
||||
TEST_CASE(TestLatexConverter, "publishing")
|
||||
{
|
||||
FontItem font("Cambria Math", 14);
|
||||
auto expression = std::make_unique<LatexMathExpression>("\\psi = \\frac{\\alpha + \\beta}{c}");
|
||||
|
||||
auto equation_node = std::make_unique<EquationNode>(Point(10, 10));
|
||||
equation_node->setContent(expression.get());
|
||||
|
||||
auto psi = LatexSymbolLookup::getSymbolUtf8("psi");
|
||||
auto alpha = LatexSymbolLookup::getSymbolUtf8("alpha");
|
||||
|
@ -20,10 +23,7 @@ TEST_CASE(TestLatexConverter, "publishing")
|
|||
|
||||
TestRenderer renderer(800, 800);
|
||||
|
||||
auto text = std::make_unique<TextNode>(content, Point(10, 10));
|
||||
text->setFont(font);
|
||||
|
||||
renderer.getScene()->addNode(text.get());
|
||||
|
||||
renderer.getScene()->addNode(equation_node.get());
|
||||
renderer.writeSvg(TestUtils::getTestOutputDir(__FILE__) / "out.svg");
|
||||
renderer.write(TestUtils::getTestOutputDir(__FILE__) / "out.png");
|
||||
};
|
|
@ -68,12 +68,23 @@ bool TestCaseRunner::run(const std::vector<std::string>& args)
|
|||
sLastTestFailed = false;
|
||||
std::cout << "TestFramework: Running Test - " << test_case->getName() << std::endl;
|
||||
|
||||
try{
|
||||
try
|
||||
{
|
||||
test_case->run();
|
||||
}
|
||||
catch (const std::runtime_error& re)
|
||||
{
|
||||
std::cout << "Failed with runtime error: " << re.what() << std::endl;
|
||||
mFailingTests.push_back(test_case->getName());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::cout << "Failed with exception: " << e.what() << std::endl;
|
||||
mFailingTests.push_back(test_case->getName());
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
std::cout << "Failed with exception" << std::endl;
|
||||
std::cout << "Failed with unknown exception" << std::endl;
|
||||
mFailingTests.push_back(test_case->getName());
|
||||
};
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include "Image.h"
|
||||
#include "PngWriter.h"
|
||||
|
||||
#include "FontsManager.h"
|
||||
|
||||
#include "File.h"
|
||||
|
||||
class TestRenderer
|
||||
|
@ -19,10 +21,14 @@ class TestRenderer
|
|||
public:
|
||||
TestRenderer(unsigned width = 1000, unsigned height = 1000)
|
||||
{
|
||||
mFontsManager = std::make_unique<FontsManager>();
|
||||
|
||||
mSurface = std::make_unique<DrawingSurface>();
|
||||
mSurface->setSize(width, height);
|
||||
|
||||
mDrawingContext = std::make_unique<DrawingContext>(mSurface.get());
|
||||
|
||||
getScene()->setFontsManager(mFontsManager.get());
|
||||
}
|
||||
|
||||
Scene* getScene() const
|
||||
|
@ -60,4 +66,6 @@ public:
|
|||
private:
|
||||
std::unique_ptr<DrawingSurface> mSurface;
|
||||
std::unique_ptr<DrawingContext> mDrawingContext;
|
||||
|
||||
std::unique_ptr<FontsManager> mFontsManager;
|
||||
};
|
Loading…
Reference in a new issue