Add initial font metrics and equation rendering.

This commit is contained in:
jmsgrogan 2023-01-25 16:51:36 +00:00
parent c2027801be
commit 5ddd54dd6d
24 changed files with 868 additions and 63 deletions

View file

@ -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;
}

View file

@ -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);
};

View file

@ -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;
}

View file

@ -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()
{
for (auto c : mRawExpression)
enum class LineState
{
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;
};

View file

@ -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;
}
}

View file

@ -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;
};

View file

@ -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);
}

View file

@ -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 };
};

View file

@ -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;
}
};

View file

@ -1,21 +1,30 @@
#include "FontsManager.h"
#ifdef _WIN32
#include "DirectWriteFontEngine.h"
#include "DirectWriteHelpers.h"
#else
#ifdef HAS_FREETYPE
#include "FreeTypeFontEngine.h"
#else
#include "BasicFontEngine.h"
#endif
#endif
#include "FontGlyph.h"
FontsManager::FontsManager()
{
#ifdef _WIN32
mFontEngine = std::make_unique<DirectWriteFontEngine>();
#else
#ifdef HAS_FREETYPE
mFontEngine = std::make_unique<FreeTypeFontEngine>();
mUsesGlyphs = true;
#else
mFontEngine = std::make_unique<BasicFontEngine>();
#endif
#endif
mFontEngine->initialize();
}

View file

@ -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;
};

View file

@ -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;
}

View file

@ -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;

View file

@ -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 if (idx == 1)
{
if (mNodeBounds)
{
return mNodeBounds.get();
}
else
{
return 0;
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});
if (mWidth == 1.0 && mHeight == 1.0)
{
mTextItem->setTextWidth(mContentWidth);
mTextItem->setTextHeight(mContentHeight);
}
else
{
mTextItem->setTextWidth(mWidth);
mTextItem->setTextHeight(mHeight);
}
mTransformIsDirty = false;
}

View file

@ -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>;

View file

@ -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));
}

View file

@ -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

View file

@ -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
list(APPEND UNIT_TEST_FILES
TestFontReader.cpp
${PLATFORM_UNIT_TEST_FILES}
PARENT_SCOPE
)
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})

View file

@ -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);
}

View file

@ -23,6 +23,7 @@ endif()
set(UNIT_TEST_FILES
TestRasterizer.cpp
TestTextRendering.cpp
${PLATFORM_UNIT_TEST_FILES}
)

View 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");
};

View file

@ -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");
};

View file

@ -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());
};

View file

@ -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;
};