#include <cstdarg>
#include <cstdlib>
#include <sstream>
#include <algorithm>
#include <Windows.h>
#include "DocTemplate.h"
std::string DocTemplate::m_currentFile = "";
unsigned int DocTemplate::m_currentLine = 0;
#define DebugMsg(fmt, ...) DocTemplate::LogMessage(LL_Debug, fmt, __VA_ARGS__)
#define WarningMsg(fmt, ...) DocTemplate::LogMessage(LL_Warning, fmt, __VA_ARGS__)
#define ErrorMsg(fmt, ...) DocTemplate::LogMessage(LL_Error, fmt, __VA_ARGS__)
//#define HAVE_CONSTEXPR
#ifdef HAVE_CONSTEXPR
// TODO: XXX ### This side of HAVE_CONSTEXPR has not been tested
constexpr unsigned int hashString(const char* str, int h = 0)
{
return !str[h] ? 5381 : (hashString(str, h + 1) * 33) ^ str[h];
}
// Assumes that for lower case comparing, the values passed in are already lower case
#define Choose(x) do { std::string tttmp = x; switch (hashString(x)) {
#define Option(x) } case hashString(x): if ( tttmp == x ) {
#define OptionLower(x) } case hashString(x): if ( str2lower(tttmp) == str2lower(std::string(x)) ) {
#define Defaults } default: {
#define Done } } while(false);
#else
#define Choose(x) do { std::string tttmp = x; if (false)
#define Option(x) } else if ( tttmp == x ) {
#define OptionLower(x) } else if ( str2lower(tttmp) == str2lower(std::string(x)) ) {
#define Defaults } else {
#define Done } while(false);
#endif
/*
Rules: Any extra white space is allowed, but if the the value, must be quoted,
eg:
text = Blah is okay
text = Foo Bar is not okay, must be quoted
text = "Foo Bar" is okay
pos = 10,10 is okay
pos = 10, 10 is not okay, must be quoted
pos = "10, 10" is okay
Removal of extra white space around the '=' is fine
eg:
pos = 10,10 is okay
pos= 10,10 is okay
pos =10,10 is okay
pos=10,10 is okay
Attributes must be deliminated by white space
eg:
foo=val1 bar=val2 is okay
foo=val1bar=val2 is not okay
foo="val1"bar=val2 is not okay (but might work as you expect, but don't count on it)
*/
DocTemplate::DocTemplate()
{
m_defaults.m_type = DIT_Unknown;
m_defaults.m_bgColor = 0xffffff;
m_defaults.m_pos.x = 10.0;
m_defaults.m_pos.y = 10.0;
m_defaults.m_size.x = 100.0;
m_defaults.m_size.y = 100.0;
m_defaults.m_font = "Helvetica";
m_defaults.m_fontSize = 12.0f;
m_defaults.m_fontColor = 0x000000;
m_defaults.m_fillColor = 0x000000;
m_defaults.m_penColor = 0x000000;
m_defaults.m_penWidth = 1.0f;
m_defaults.m_penStyle = PS_Solid;
m_defaults.m_text = "undefined";
m_defaults.m_imageFile = "undefined";
}
DocTemplate::~DocTemplate()
{
}
#ifndef _WIN32
int _vscprintf(const char * format, va_list pargs)
{
int retval;
va_list argcopy;
va_copy(argcopy, pargs);
retval = vsnprintf(NULL, 0, format, argcopy);
va_end(argcopy);
return retval;
}
#endif
void DocTemplate::LogMessage(yqLogLevel a_level, const char* a_msg, ...)
{
va_list args;
va_start(args, a_msg);
auto sizeForFileLineAndNull = m_currentFile.size() + 64 + 1;
auto sizeRequired = _vscprintf(a_msg, args) + sizeForFileLineAndNull;
auto a = (char*)_alloca(sizeRequired);
int offset = _snprintf_s(a, sizeRequired, sizeForFileLineAndNull, "%s(%i): ", m_currentFile.c_str(), m_currentLine);
vsnprintf_s(a + offset, sizeRequired - offset, INT_MAX - 1, a_msg, args);
OutputDebugStringA(a);
_freea(a);
va_end(args);
/*
if (a_level == LL_Warning)
DebugBreak();
*/
if (a_level == LL_Error)
exit(-1);
}
std::vector<std::string> split(const std::string &s, char delim)
{
std::vector<std::string> elems;
std::stringstream ss(s);
std::string item;
while (std::getline(ss, item, delim)) {
elems.push_back(item);
}
return elems;
}
std::string trim(std::string& str)
{
str.erase(0, str.find_first_not_of(" \t\r\n"));
str.erase(str.find_last_not_of(" \t\r\n") + 1);
return str;
}
std::string str2lower(std::string& str)
{
std::string ret = str;
std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower);
return ret;
}
unsigned int str2col(std::string& str)
{
if (str[0] == '#')
str = &str[1];
return std::stoul(str, nullptr, 16);
}
std::vector<Point> str2pointList(std::string& str)
{
std::vector<Point> ret;
std::vector<std::string> strs = split(str, ',');
for (int i = 0; i < strs.size(); i+=2)
{
Point pnt = { 0, 0 };
pnt.x = (float)atof(trim(strs[i + 0]).c_str());
if ((i + 1) < strs.size())
pnt.y = (float)atof(trim(strs[i + 1]).c_str());
else
WarningMsg("uneven coordinate count in shape, expecting a list of 2d coordinates so should be even count!\n");
ret.push_back(pnt);
}
return ret;
}
Point str2pos(std::string& str)
{
Point ret = { 0, 0 };
std::vector<std::string> strs = split(str, ',');
ret.x = (float)atof(trim(strs[0]).c_str());
ret.y = (float)atof(trim(strs[1]).c_str());
return ret;
}
float str2float(std::string& str)
{
return (float)atof(str.c_str());
}
PenStyle str2style(std::string& str)
{
Choose(str)
{
OptionLower("Solid")
return PS_Solid;
OptionLower("Dashed")
return PS_Dashed;
Defaults
WarningMsg("Bad style\n");
break;
}
Done
return PS_Solid;
}
Align str2align(std::string& str)
{
unsigned int alignment = 0;
std::vector<std::string> strs = split(str, ',');
std::string horzStr = trim(strs[0]);
std::string vertStr = trim(strs[1]);
Choose(horzStr)
{
OptionLower("left") alignment |= AL_Left; break;
OptionLower("right") alignment |= AL_Right; break;
OptionLower("top") alignment |= AL_Top; break;
OptionLower("bottom") alignment |= AL_Bottom; break;
OptionLower("center") alignment |= AL_HCenter; break;
OptionLower("centre") alignment |= AL_HCenter; break;
Defaults
WarningMsg("Bad horizontal alignment\n");
break;
}
Done
Choose(vertStr)
{
OptionLower("left") alignment |= AL_Left; break;
OptionLower("right") alignment |= AL_Right; break;
OptionLower("top") alignment |= AL_Top; break;
OptionLower("bottom") alignment |= AL_Bottom; break;
OptionLower("center") alignment |= AL_VCenter; break;
OptionLower("centre") alignment |= AL_VCenter; break;
Defaults
WarningMsg("Bad horizontal alignment\n");
break;
}
Done
// check we have a valid combination
if ((alignment & (AL_Left | AL_HCenter | AL_Right)) && (alignment & (AL_Top | AL_VCenter | AL_Bottom)))
return (Align)alignment;
// for it to still be valid, then one of the values is a center, swap the center types and try again
if (alignment & AL_HCenter)
alignment = (alignment & ~AL_HCenter) | AL_VCenter;
else if (alignment & AL_VCenter)
alignment = (alignment & ~AL_VCenter) | AL_HCenter;
if ((alignment & (AL_Left | AL_HCenter | AL_Right)) && (alignment & (AL_Top | AL_VCenter | AL_Bottom)))
return (Align)alignment;
WarningMsg("Bad alignment values\n");
return (Align)(AL_Top | AL_Left);
}
void DocTemplate::ReadTemplateFile(const char* a_fileName)
{
m_items.clear();
FILE* f = 0;
fopen_s(&f, a_fileName, "rb");
if ( f )
{
m_currentFile = a_fileName;
char lineBuf[1024];
DocTemplateItem currentItem = m_defaults;
bool first = true;
while ( fgets(lineBuf, sizeof(lineBuf), f) ) {
m_currentLine++;
if (lineBuf[0] == '[') {
// new item
if (!first) {
if (currentItem.m_type == DIT_Defaults)
m_defaults = currentItem;
else
m_items.push_back(currentItem);
currentItem = m_defaults;
}
first = false;
Choose(trim(std::string(lineBuf)))
{
OptionLower("[Defaults]")
currentItem.m_type = DIT_Defaults;
break;
#define STR_SWITCH_CASE( OPT ) \
OptionLower("[" #OPT "]") \
currentItem.m_type = DIT_##OPT; \
break;
OptionLower("[Text]") // Alias for Label
currentItem.m_type = DIT_Label;
break;
STR_SWITCH_CASE(Label)
STR_SWITCH_CASE(Background)
STR_SWITCH_CASE(Image)
STR_SWITCH_CASE(Line)
STR_SWITCH_CASE(Box)
STR_SWITCH_CASE(Circle)
STR_SWITCH_CASE(Ellipse)
STR_SWITCH_CASE(Polygon)
STR_SWITCH_CASE(Spline)
STR_SWITCH_CASE(Shape)
Defaults
currentItem.m_type = DIT_Unknown;
break;
}
Done
} else {
std::vector<std::string> strs = split(lineBuf, '=');
std::string s = (strs.size() > 0) ? trim(strs[0]) : "";
if (strs.size() != 2)
{
if (s != "")
WarningMsg("Error parsing line, no '=' or line is whitespace.\n");
continue;
}
Choose(s)
{
//OptionLower("id")
//OptionLower("flags")// wrap, ignore, clip
OptionLower("bgColor")
currentItem.m_bgColor = str2col(trim(strs[1]));
break;
OptionLower("align")
currentItem.m_alignment = str2align(trim(strs[1]));
break;
OptionLower("pos")
currentItem.m_pos = str2pos(trim(strs[1]));
break;
OptionLower("size")
currentItem.m_size = str2pos(trim(strs[1]));
break;
OptionLower("font")
currentItem.m_font = trim(strs[1]);
break;
OptionLower("fontSize")
currentItem.m_fontSize = str2float(trim(strs[1]));
break;
OptionLower("color") // alias for all the foreground colors
currentItem.m_fontColor = str2col(trim(strs[1]));
currentItem.m_fillColor = str2col(trim(strs[1]));
currentItem.m_penColor = str2col(trim(strs[1]));
break;
OptionLower("fontColor")
currentItem.m_fontColor = str2col(trim(strs[1]));
break;
OptionLower("fillColor")
currentItem.m_fillColor = str2col(trim(strs[1]));
break;
OptionLower("penColor")
currentItem.m_penColor = str2col(trim(strs[1]));
break;
OptionLower("penSize") // alias for penWidth
currentItem.m_penWidth = str2float(trim(strs[1]));
break;
OptionLower("penWidth")
currentItem.m_penWidth = str2float(trim(strs[1]));
break;
OptionLower("penStyle")
currentItem.m_penStyle = str2style(trim(strs[1]));
break;
OptionLower("text")
currentItem.m_text = trim(strs[1]);
break;
OptionLower("points")
currentItem.m_shape = str2pointList(trim(strs[1]));
break;
OptionLower("file")
currentItem.m_imageFile = trim(strs[1]);
break;
Defaults
if (s != "" && strs.size() != 1)
currentItem.m_type = DIT_Unknown;
break;
}
Done
}
/*
Image ( pos={0,0} size={w,h} file="image.jpg" )
Line ( pos={0,0} size={w,h} points={20,20,30,30,40,20} pen={"#000",1.4,"solid"} )
Shape ( pos={0,0} size={w,h} points={20,20,30,30,40,20} fill="#fff" pen={"#000",1.4,"solid"} )
//box, circle, ellipse, poly, spline, label, image
*/
}
if (currentItem.m_type == DIT_Defaults)
m_defaults = currentItem;
else
m_items.push_back(currentItem);
fclose(f);
}
else
{
WarningMsg("Problem opening file\n");
}
}
void DocTemplate::Apply(DocOutputPage& page)
{
for (int i = 0; i < m_items.size(); ++i)
{
if (m_items[i].m_type == DIT_Label)
{
float x = m_items[i].m_pos.x;
float y = m_items[i].m_pos.y;
if (x < 0.0) x = x + page.width(); // negative numbers are aligned from the right
if (y < 0.0) y = y + page.height(); // negative numbers are aligned from the bottom
page.setFontSize(0, m_items[i].m_fontSize); // TODO: map font name to a font index or make the output accept a string
page.setColor(m_items[i].m_fontColor);
page.drawText(x, y, m_items[i].m_text.c_str());
}
}
page.drawImage("test/logo.png", 10, 10, 100, 100);
}
void DocTemplate::WriteTemplateFile(const char* /*a_fileName*/)
{
}