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");
|
throw std::logic_error("Not implemented");
|
||||||
#endif
|
#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
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class UnicodeUtils
|
class UnicodeUtils
|
||||||
{
|
{
|
||||||
|
@ -8,4 +9,12 @@ public:
|
||||||
static std::string utf16ToUtf8String(const std::wstring& input);
|
static std::string utf16ToUtf8String(const std::wstring& input);
|
||||||
|
|
||||||
static std::wstring utf8ToUtf16WString(const std::string& 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 <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
class LatexMathExpression;
|
||||||
|
using LatexMathExpressionPtr = std::unique_ptr<LatexMathExpression>;
|
||||||
|
|
||||||
class LatexMathExpression
|
class LatexMathExpression
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LatexMathExpression(const std::string& expression = {})
|
enum class Type
|
||||||
: mRawExpression(expression)
|
|
||||||
{
|
{
|
||||||
|
LINEAR,
|
||||||
|
LEAF,
|
||||||
|
FRACTION,
|
||||||
|
SUBSCRIPT,
|
||||||
|
SUPERSCRIPT
|
||||||
|
};
|
||||||
|
|
||||||
}
|
enum class LineState
|
||||||
|
|
||||||
void parse()
|
|
||||||
{
|
|
||||||
for (auto c : mRawExpression)
|
|
||||||
{
|
{
|
||||||
|
NONE,
|
||||||
|
IN_TAG_NAME,
|
||||||
|
AWAITING_LBRACE,
|
||||||
|
IN_TAG_BODY
|
||||||
|
};
|
||||||
|
|
||||||
}
|
LatexMathExpression(const std::string& expression);
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<LatexMathSymbol>& getSymbols() const
|
LatexMathExpression(Type type);
|
||||||
{
|
|
||||||
return mSymbols;
|
~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:
|
private:
|
||||||
|
std::size_t mOpenBraceCount{ 0 };
|
||||||
|
|
||||||
unsigned mOpenTagCount{ 0 };
|
LineState mLineState{ LineState::NONE };
|
||||||
|
std::string mWorkingString;
|
||||||
|
std::string mRawBody;
|
||||||
|
|
||||||
std::string mRawExpression;
|
Type mWorkingType{ Type::LEAF };
|
||||||
std::unique_ptr<LatexMathExpression> mSuperScriptExpr;
|
LatexMathExpression* mWorkingExpression{ nullptr };
|
||||||
std::unique_ptr<LatexMathExpression> mSubScriptExpr;
|
Type mType{ Type::LEAF };
|
||||||
std::unique_ptr<LatexMathExpression> mEnclosedExpr;
|
std::vector<LatexMathSymbol> mWorkingSymbols;
|
||||||
|
|
||||||
std::vector<LatexMathSymbol> mSymbols;
|
std::vector<LatexMathSymbol> mSymbols;
|
||||||
|
std::vector<LatexMathExpressionPtr> mExpressions;
|
||||||
};
|
};
|
|
@ -10,6 +10,11 @@ std::unordered_map<std::string, std::wstring> LatexSymbolLookup::mSymbols = {
|
||||||
{"psi", L"\u03C8"}
|
{"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)
|
std::optional<std::string> LatexSymbolLookup::getSymbolUtf8(const std::string& tag)
|
||||||
{
|
{
|
||||||
if (auto entry = getSymbolUtf16(tag); entry)
|
if (auto entry = getSymbolUtf16(tag); entry)
|
||||||
|
@ -33,3 +38,15 @@ std::optional<std::wstring> LatexSymbolLookup::getSymbolUtf16(const std::string&
|
||||||
return std::nullopt;
|
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,
|
||||||
ENCLOSING_TAG
|
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::string mName;
|
||||||
std::wstring mUnicode;
|
std::string mUnicode;
|
||||||
};
|
};
|
||||||
|
|
||||||
class LatexSymbolLookup
|
class LatexSymbolLookup
|
||||||
|
@ -26,6 +42,21 @@ public:
|
||||||
|
|
||||||
static std::optional<std::wstring> getSymbolUtf16(const std::string& tag);
|
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:
|
private:
|
||||||
static std::unordered_map<std::string, std::wstring> mSymbols;
|
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 "EquationNode.h"
|
||||||
|
|
||||||
|
#include "LatexMathExpression.h"
|
||||||
|
#include "TextNode.h"
|
||||||
|
|
||||||
|
#include "FileLogger.h"
|
||||||
|
|
||||||
EquationNode::EquationNode(const Transform& t)
|
EquationNode::EquationNode(const Transform& t)
|
||||||
: AbstractVisualNode(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
|
#pragma once
|
||||||
|
|
||||||
#include "AbstractVisualNode.h"
|
#include "AbstractVisualNode.h"
|
||||||
|
#include "FontItem.h"
|
||||||
|
|
||||||
|
class TextNode;
|
||||||
|
class LatexMathExpression;
|
||||||
|
|
||||||
class EquationNode : public AbstractVisualNode
|
class EquationNode : public AbstractVisualNode
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
EquationNode(const Transform& t = {});
|
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 void loadFontFace(const std::filesystem::path&, float){};
|
||||||
|
|
||||||
virtual std::unique_ptr<FontGlyph> loadGlyph(unsigned){return nullptr;};
|
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"
|
#include "FontsManager.h"
|
||||||
|
|
||||||
#ifdef HAS_FREETYPE
|
#ifdef _WIN32
|
||||||
#include "FreeTypeFontEngine.h"
|
#include "DirectWriteFontEngine.h"
|
||||||
|
#include "DirectWriteHelpers.h"
|
||||||
#else
|
#else
|
||||||
#include "BasicFontEngine.h"
|
#ifdef HAS_FREETYPE
|
||||||
|
#include "FreeTypeFontEngine.h"
|
||||||
|
#else
|
||||||
|
#include "BasicFontEngine.h"
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "FontGlyph.h"
|
#include "FontGlyph.h"
|
||||||
|
|
||||||
FontsManager::FontsManager()
|
FontsManager::FontsManager()
|
||||||
{
|
{
|
||||||
#ifdef HAS_FREETYPE
|
#ifdef _WIN32
|
||||||
|
mFontEngine = std::make_unique<DirectWriteFontEngine>();
|
||||||
|
#else
|
||||||
|
#ifdef HAS_FREETYPE
|
||||||
mFontEngine = std::make_unique<FreeTypeFontEngine>();
|
mFontEngine = std::make_unique<FreeTypeFontEngine>();
|
||||||
mUsesGlyphs = true;
|
mUsesGlyphs = true;
|
||||||
#else
|
#else
|
||||||
mFontEngine = std::make_unique<BasicFontEngine>();
|
mFontEngine = std::make_unique<BasicFontEngine>();
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
mFontEngine->initialize();
|
mFontEngine->initialize();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "FontItem.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
@ -16,4 +18,6 @@ public:
|
||||||
virtual void loadFontFace(const std::filesystem::path& fontFile, float penSize = 16) = 0;
|
virtual void loadFontFace(const std::filesystem::path& fontFile, float penSize = 16) = 0;
|
||||||
|
|
||||||
virtual std::unique_ptr<FontGlyph> loadGlyph(uint32_t charCode) = 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 "UnicodeUtils.h"
|
||||||
|
|
||||||
#include "DirectWriteHelpers.h"
|
#include "DirectWriteHelpers.h"
|
||||||
|
#include "FileLogger.h"
|
||||||
|
|
||||||
#include <dwrite.h>
|
#include <dwrite.h>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
void DirectWriteFontEngine::initialize()
|
void DirectWriteFontEngine::initialize()
|
||||||
{
|
{
|
||||||
|
@ -35,6 +37,83 @@ std::unique_ptr<FontGlyph> DirectWriteFontEngine::loadGlyph(uint32_t charCode)
|
||||||
return nullptr;
|
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)
|
std::unique_ptr<GlyphRunOutlines> DirectWriteFontEngine::getGlyphRunOutlines(const std::vector<uint32_t>& charCodes)
|
||||||
{
|
{
|
||||||
initializeOffScreenRenderer();
|
initializeOffScreenRenderer();
|
||||||
|
@ -52,3 +131,42 @@ std::unique_ptr<GlyphRunOutlines> DirectWriteFontEngine::getGlyphRunOutlines(con
|
||||||
return mOffScreenRenderer->getGlypyRunOutline();
|
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 "IFontEngine.h"
|
||||||
#include "FontGlyph.h"
|
#include "FontGlyph.h"
|
||||||
|
#include "FontItem.h"
|
||||||
|
|
||||||
#include <wrl.h>
|
#include <wrl.h>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
class GlyphOutlineGeometrySink;
|
class GlyphOutlineGeometrySink;
|
||||||
class OffScreenTextRenderer;
|
class OffScreenTextRenderer;
|
||||||
|
@ -13,6 +15,8 @@ class OffScreenTextRenderer;
|
||||||
struct IDWriteFactory;
|
struct IDWriteFactory;
|
||||||
struct IDWriteTextFormat;
|
struct IDWriteTextFormat;
|
||||||
struct IDWriteTextLayout;
|
struct IDWriteTextLayout;
|
||||||
|
struct IDWriteFontFace;
|
||||||
|
struct IDWriteFontFamily;
|
||||||
|
|
||||||
class DirectWriteFontEngine : public IFontEngine
|
class DirectWriteFontEngine : public IFontEngine
|
||||||
{
|
{
|
||||||
|
@ -27,10 +31,24 @@ public:
|
||||||
|
|
||||||
std::unique_ptr<GlyphRunOutlines> getGlyphRunOutlines(const std::vector<uint32_t>& charCodes);
|
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:
|
private:
|
||||||
void initializeOffScreenRenderer();
|
void initializeOffScreenRenderer();
|
||||||
|
|
||||||
bool mIsValid{ false };
|
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<IDWriteFactory> mDWriteFactory;
|
||||||
Microsoft::WRL::ComPtr<IDWriteTextFormat> mTextFormat;
|
Microsoft::WRL::ComPtr<IDWriteTextFormat> mTextFormat;
|
||||||
Microsoft::WRL::ComPtr<IDWriteTextLayout> mTextLayout;
|
Microsoft::WRL::ComPtr<IDWriteTextLayout> mTextLayout;
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
#include "SceneInfo.h"
|
#include "SceneInfo.h"
|
||||||
|
|
||||||
#include "SceneText.h"
|
#include "SceneText.h"
|
||||||
|
#include "SceneModel.h"
|
||||||
|
#include "Rectangle.h"
|
||||||
|
|
||||||
#include "Color.h"
|
#include "Color.h"
|
||||||
|
|
||||||
|
@ -18,6 +20,8 @@ TextNode::TextNode(const std::string& content, const Transform& transform)
|
||||||
: MaterialNode(transform)
|
: MaterialNode(transform)
|
||||||
{
|
{
|
||||||
mTextData.mContent= content;
|
mTextData.mContent= content;
|
||||||
|
setFillColor(Color(0, 0, 0));
|
||||||
|
setHasStrokeColor(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextNode::~TextNode()
|
TextNode::~TextNode()
|
||||||
|
@ -92,15 +96,39 @@ SceneItem* TextNode::getSceneItem(std::size_t idx) const
|
||||||
{
|
{
|
||||||
return mTextItem.get();
|
return mTextItem.get();
|
||||||
}
|
}
|
||||||
|
else if (idx == 1)
|
||||||
|
{
|
||||||
|
if (mNodeBounds)
|
||||||
|
{
|
||||||
|
return mNodeBounds.get();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return 0;
|
return mTextBounds.get();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else if (idx == 2)
|
||||||
|
{
|
||||||
|
if (mTextBounds)
|
||||||
|
{
|
||||||
|
return mTextBounds.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t TextNode::getNumSceneItems() const
|
std::size_t TextNode::getNumSceneItems() const
|
||||||
{
|
{
|
||||||
return 1;
|
auto count = 1;
|
||||||
|
if (mNodeBounds)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (mTextBounds)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextNode::updateLines(FontsManager* fontsManager)
|
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)
|
void TextNode::update(SceneInfo* sceneInfo)
|
||||||
{
|
{
|
||||||
if (!mTextItem)
|
if (!mTextItem)
|
||||||
{
|
{
|
||||||
mTextItem = std::make_unique<SceneText>();
|
mTextItem = std::make_unique<SceneText>();
|
||||||
mTextItem->setName(mName + "_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)
|
if (mTransformIsDirty || mContentIsDirty)
|
||||||
|
@ -167,9 +241,16 @@ void TextNode::update(SceneInfo* sceneInfo)
|
||||||
|
|
||||||
if (mTransformIsDirty)
|
if (mTransformIsDirty)
|
||||||
{
|
{
|
||||||
//mTextItem->updateTransform({mLocation});
|
if (mWidth == 1.0 && mHeight == 1.0)
|
||||||
|
{
|
||||||
|
mTextItem->setTextWidth(mContentWidth);
|
||||||
|
mTextItem->setTextHeight(mContentHeight);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
mTextItem->setTextWidth(mWidth);
|
mTextItem->setTextWidth(mWidth);
|
||||||
mTextItem->setTextHeight(mHeight);
|
mTextItem->setTextHeight(mHeight);
|
||||||
|
}
|
||||||
mTransformIsDirty = false;
|
mTransformIsDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class FontsManager;
|
class FontsManager;
|
||||||
|
class SceneModel;
|
||||||
|
|
||||||
class TextNode : public MaterialNode
|
class TextNode : public MaterialNode
|
||||||
{
|
{
|
||||||
|
@ -34,9 +35,11 @@ public:
|
||||||
void setHeight(double height);
|
void setHeight(double height);
|
||||||
|
|
||||||
void setContent(const std::string& content);
|
void setContent(const std::string& content);
|
||||||
|
|
||||||
void setFont(const FontItem& font);
|
void setFont(const FontItem& font);
|
||||||
|
|
||||||
|
void setRenderNodeBounds(bool render);
|
||||||
|
void setRenderTextBounds(bool render);
|
||||||
|
|
||||||
void update(SceneInfo* sceneInfo) override;
|
void update(SceneInfo* sceneInfo) override;
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
@ -49,7 +52,15 @@ private:
|
||||||
double mWidth{1};
|
double mWidth{1};
|
||||||
double mHeight{1};
|
double mHeight{1};
|
||||||
|
|
||||||
|
double mContentWidth{ 1 };
|
||||||
|
double mContentHeight{ 1 };
|
||||||
|
|
||||||
std::unique_ptr<SceneText> mTextItem;
|
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>;
|
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>();
|
auto svg_text = std::make_unique<SvgTextElement>();
|
||||||
svg_text->setContent(text->getTextData().mContent);
|
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->setFontFamily(text->getTextData().mFont.getFaceName());
|
||||||
|
|
||||||
svg_text->setFill(text->getSolidMaterial().getFillColor());
|
svg_text->setFill(text->getSolidMaterial().getFillColor());
|
||||||
|
@ -248,9 +246,14 @@ void SvgPainter::paintText(SvgDocument* document, SceneText* text) const
|
||||||
{
|
{
|
||||||
svg_text->setFillOpacity(opacity);
|
svg_text->setFillOpacity(opacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
svg_text->setFontSize(text->getTextData().mFont.getSize());
|
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));
|
document->getRoot()->addChild(std::move(svg_text));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
add_subdirectory(test_utils)
|
add_subdirectory(test_utils)
|
||||||
|
|
||||||
|
add_subdirectory(fonts)
|
||||||
add_subdirectory(geometry)
|
add_subdirectory(geometry)
|
||||||
add_subdirectory(graphics)
|
add_subdirectory(graphics)
|
||||||
add_subdirectory(publishing)
|
add_subdirectory(publishing)
|
||||||
|
@ -13,7 +14,6 @@ set(TEST_MODULES
|
||||||
compression
|
compression
|
||||||
core
|
core
|
||||||
database
|
database
|
||||||
fonts
|
|
||||||
image
|
image
|
||||||
ipc
|
ipc
|
||||||
network
|
network
|
||||||
|
|
|
@ -4,23 +4,24 @@ if(UNIX)
|
||||||
find_package(Freetype QUIET)
|
find_package(Freetype QUIET)
|
||||||
if(Freetype_FOUND)
|
if(Freetype_FOUND)
|
||||||
set(PLATFORM_UNIT_TEST_FILES
|
set(PLATFORM_UNIT_TEST_FILES
|
||||||
fonts/TestFreeTypeFontEngine.cpp
|
TestFreeTypeFontEngine.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
set(PLATFORM_UNIT_TEST_FILES
|
set(PLATFORM_UNIT_TEST_FILES
|
||||||
fonts/TestDirectWriteFontEngine.cpp
|
TestDirectWriteFontEngine.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
set(MODULE_NAME fonts)
|
||||||
|
|
||||||
set(FONTS_UNIT_TEST_FILES
|
list(APPEND UNIT_TEST_FILES
|
||||||
fonts/TestFontReader.cpp
|
TestFontReader.cpp
|
||||||
${PLATFORM_UNIT_TEST_FILES}
|
${PLATFORM_UNIT_TEST_FILES}
|
||||||
PARENT_SCOPE
|
)
|
||||||
)
|
|
||||||
|
|
||||||
set(FONTS_UNIT_TEST_DEPENDENCIES
|
set(UNIT_TEST_TARGET_NAME ${MODULE_NAME}_unit_tests)
|
||||||
fonts
|
|
||||||
PARENT_SCOPE
|
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 "SvgWriter.h"
|
||||||
#include "SvgShapeElements.h"
|
#include "SvgShapeElements.h"
|
||||||
#include "File.h"
|
#include "File.h"
|
||||||
|
#include "FontItem.h"
|
||||||
|
|
||||||
TEST_CASE(TestDirectWriteFontEngine, "fonts")
|
TEST_CASE(TestDirectWriteFontEngine, "fonts")
|
||||||
{
|
{
|
||||||
|
@ -34,3 +35,16 @@ TEST_CASE(TestDirectWriteFontEngine, "fonts")
|
||||||
file.writeText(doc_string);
|
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
|
set(UNIT_TEST_FILES
|
||||||
TestRasterizer.cpp
|
TestRasterizer.cpp
|
||||||
|
TestTextRendering.cpp
|
||||||
${PLATFORM_UNIT_TEST_FILES}
|
${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 "TestRenderUtils.h"
|
||||||
#include "StringUtils.h"
|
#include "StringUtils.h"
|
||||||
|
|
||||||
#include "FontItem.h"
|
#include "EquationNode.h"
|
||||||
#include "TextNode.h"
|
#include "LatexMathExpression.h"
|
||||||
#include "LatexSymbols.h"
|
|
||||||
|
|
||||||
TEST_CASE(TestLatexConverter, "publishing")
|
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 psi = LatexSymbolLookup::getSymbolUtf8("psi");
|
||||||
auto alpha = LatexSymbolLookup::getSymbolUtf8("alpha");
|
auto alpha = LatexSymbolLookup::getSymbolUtf8("alpha");
|
||||||
|
@ -20,10 +23,7 @@ TEST_CASE(TestLatexConverter, "publishing")
|
||||||
|
|
||||||
TestRenderer renderer(800, 800);
|
TestRenderer renderer(800, 800);
|
||||||
|
|
||||||
auto text = std::make_unique<TextNode>(content, Point(10, 10));
|
renderer.getScene()->addNode(equation_node.get());
|
||||||
text->setFont(font);
|
renderer.writeSvg(TestUtils::getTestOutputDir(__FILE__) / "out.svg");
|
||||||
|
|
||||||
renderer.getScene()->addNode(text.get());
|
|
||||||
|
|
||||||
renderer.write(TestUtils::getTestOutputDir(__FILE__) / "out.png");
|
renderer.write(TestUtils::getTestOutputDir(__FILE__) / "out.png");
|
||||||
};
|
};
|
|
@ -68,12 +68,23 @@ bool TestCaseRunner::run(const std::vector<std::string>& args)
|
||||||
sLastTestFailed = false;
|
sLastTestFailed = false;
|
||||||
std::cout << "TestFramework: Running Test - " << test_case->getName() << std::endl;
|
std::cout << "TestFramework: Running Test - " << test_case->getName() << std::endl;
|
||||||
|
|
||||||
try{
|
try
|
||||||
|
{
|
||||||
test_case->run();
|
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(...)
|
catch(...)
|
||||||
{
|
{
|
||||||
std::cout << "Failed with exception" << std::endl;
|
std::cout << "Failed with unknown exception" << std::endl;
|
||||||
mFailingTests.push_back(test_case->getName());
|
mFailingTests.push_back(test_case->getName());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
#include "Image.h"
|
#include "Image.h"
|
||||||
#include "PngWriter.h"
|
#include "PngWriter.h"
|
||||||
|
|
||||||
|
#include "FontsManager.h"
|
||||||
|
|
||||||
#include "File.h"
|
#include "File.h"
|
||||||
|
|
||||||
class TestRenderer
|
class TestRenderer
|
||||||
|
@ -19,10 +21,14 @@ class TestRenderer
|
||||||
public:
|
public:
|
||||||
TestRenderer(unsigned width = 1000, unsigned height = 1000)
|
TestRenderer(unsigned width = 1000, unsigned height = 1000)
|
||||||
{
|
{
|
||||||
|
mFontsManager = std::make_unique<FontsManager>();
|
||||||
|
|
||||||
mSurface = std::make_unique<DrawingSurface>();
|
mSurface = std::make_unique<DrawingSurface>();
|
||||||
mSurface->setSize(width, height);
|
mSurface->setSize(width, height);
|
||||||
|
|
||||||
mDrawingContext = std::make_unique<DrawingContext>(mSurface.get());
|
mDrawingContext = std::make_unique<DrawingContext>(mSurface.get());
|
||||||
|
|
||||||
|
getScene()->setFontsManager(mFontsManager.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
Scene* getScene() const
|
Scene* getScene() const
|
||||||
|
@ -60,4 +66,6 @@ public:
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<DrawingSurface> mSurface;
|
std::unique_ptr<DrawingSurface> mSurface;
|
||||||
std::unique_ptr<DrawingContext> mDrawingContext;
|
std::unique_ptr<DrawingContext> mDrawingContext;
|
||||||
|
|
||||||
|
std::unique_ptr<FontsManager> mFontsManager;
|
||||||
};
|
};
|
Loading…
Reference in a new issue