Newer
Older
Import / applications / MakePDF / DocSVG.h
#ifndef DOC_SVG_H
#define DOC_SVG_H


#include <vector>
#include <map>
#include "tinyxml.h"
#include "DocStyle.h"
#include "DocTemplate.h"
#include "DocOutput.h"
#include "hpdf.h"


class SVGOperation
{
public:
	enum PathOperation {
		MoveToAbs,
		LineToAbs,
		HorizontalLineToAbs,
		VerticalLineToAbs,
		CurveToAbs,
		SmoothCurveToAbs,
		QuadraticBezierCurveToAbs,
		SmoothQuadraticBezierCurveToAbs,
		EllipticalArcToAbs,
		ClosePath,

		MoveToRel,
		LineToRel,
		HorizontalLineToRel,
		VerticalLineToRel,
		CurveToRel,
		SmoothCurveToRel,
		QuadraticBezierCurveToRel,
		SmoothQuadraticBezierCurveToRel,
		EllipticalArcToRel,
		AltClosePath,

		BadOperation,

		BeginPathOperation,
		EndPathOperation
	};

	static const char PathOperationChar[];
	static const int PathOperationArgs[];

	static PathOperation getOperationForChar(char a_ch)
  {
		for (int i = 0; i < BadOperation; i++)
			if (PathOperationChar[i] == a_ch)
				return (PathOperation)i;
		return BadOperation;
	}

	int getArgCount()
  {
		return PathOperationArgs[m_type];
	}

	void dump()
  {
		fprintf(stderr, " OP: -%c- ARGS: ", PathOperationChar[m_type]);
		for (int i = 0; i < PathOperationArgs[m_type]; i++)
			fprintf(stderr, " -%f- ", m_values[i]);
		fprintf(stderr, "\n");
	}

	void scale(double a_scale)
  {
		for (int i = 0; i < PathOperationArgs[m_type]; i++)
			m_values[i] *= a_scale;
	}

  void output(HPDF_Page page, float curPos[2], bool /*fill*/, PathOperation prevOp, float pathStartPos[2])
  {
		float pageHeight = HPDF_Page_GetHeight(page);
		float x = m_values[0];
		float y = m_values[1];
		fprintf(stderr, "Output:  OP: -%c- ARGS: %f %f \n", PathOperationChar[m_type], x, y);
		switch (m_type) {
			case MoveToAbs:
				curPos[0] = x; curPos[1] = y;
				HPDF_Page_MoveTo(page, curPos[0], pageHeight - curPos[1]);
				fprintf(stderr, "move to: %f %f\n", curPos[0], curPos[1]);
				break;
			case LineToAbs:
				curPos[0] = x; curPos[1] = y;
				HPDF_Page_LineTo(page, curPos[0], pageHeight - curPos[1]);
				fprintf(stderr, "line to: %f %f\n", curPos[0], curPos[1]);
				break;
			case HorizontalLineToAbs:
				curPos[0] = x;
				HPDF_Page_LineTo(page, curPos[0], pageHeight - curPos[1]);
				break;
			case VerticalLineToAbs:
				curPos[1] = x;
				HPDF_Page_LineTo(page, curPos[0], pageHeight - curPos[1]);
				break;
			case CurveToAbs:
				HPDF_Page_CurveTo(page,
					m_values[0], pageHeight - m_values[1],
					m_values[2], pageHeight - m_values[3],
					m_values[4], pageHeight - m_values[5]);
				curPos[0] = m_values[4];
				curPos[1] = m_values[5];
				break;
			case SmoothCurveToAbs:
				HPDF_Page_CurveTo2(page,
					m_values[0], pageHeight - m_values[1],
					m_values[2], pageHeight - m_values[3]);
				curPos[0] = m_values[2];
				curPos[1] = m_values[3];
				break;
			case QuadraticBezierCurveToAbs:
				//HPDF_Page_CurveTo2(HPDF_Page, HPDF_REAL x2, HPDF_REAL y2, HPDF_REAL x3, HPDF_REAL y3);
				//HPDF_Page_CurveTo3(HPDF_Page, HPDF_REAL x1, HPDF_REAL y1, HPDF_REAL x3, HPDF_REAL y3);
				// HPDF_Page_CurveTo3 // ??
				HPDF_Page_CurveTo2(page,
					m_values[0], pageHeight - m_values[1],
					m_values[2], pageHeight - m_values[3]);
				curPos[0] = m_values[2];
				curPos[1] = m_values[3];
				break;
			case SmoothQuadraticBezierCurveToAbs:
				// No idea, only has 2 args, I guess an x and y, but how does this make a curve?
				break;
			case EllipticalArcToAbs:
				// SVG has 7 args, PDF has 5!
				//HPDF_Page_Arc  (HPDF_Page    page, HPDF_REAL    x, HPDF_REAL    y,
				//		HPDF_REAL    ray,  HPDF_REAL    ang1, HPDF_REAL    ang2);
				break;
			case MoveToRel:
				if (prevOp == BeginPathOperation) { // TODO: Perhaps should check this is really needed
					curPos[0] = x; curPos[1] = y;
				} else {
					curPos[0] += x; curPos[1] += y;
				}
				//curPos[0] += x; curPos[1] += y;
				HPDF_Page_MoveTo(page, curPos[0], pageHeight - curPos[1]);
				break;
			case LineToRel:
				if (prevOp == BeginPathOperation) { // TODO: Perhaps should check this is really needed
					curPos[0] = x; curPos[1] = y;
				} else {
					curPos[0] += x; curPos[1] += y;
				}
				HPDF_Page_LineTo(page, curPos[0], pageHeight - curPos[1]);
				break;
			case HorizontalLineToRel:
				curPos[0] += x;
				HPDF_Page_LineTo(page, curPos[0], pageHeight - curPos[1]);
				break;
			case VerticalLineToRel:
				curPos[1] += x;
				HPDF_Page_LineTo(page, curPos[0], pageHeight - curPos[1]);
				break;
			case CurveToRel:
				// TODO: check if these are cumulatively relative, or all rel to current pos
				HPDF_Page_CurveTo(page,
					curPos[0] + m_values[0], pageHeight - curPos[1] - m_values[1],
					curPos[0] + m_values[2], pageHeight - curPos[1] - m_values[3],
					curPos[0] + m_values[4], pageHeight - curPos[1] - m_values[5]);
				curPos[0] += m_values[4];
				curPos[1] += m_values[5];
				break;
			case SmoothCurveToRel:
				// TODO: check if these are cumulatively relative, or all rel to current pos
				HPDF_Page_CurveTo2(page,
					curPos[0] + m_values[0], pageHeight - curPos[1] - m_values[1],
					curPos[0] + m_values[2], pageHeight - curPos[1] - m_values[3]);
				curPos[0] += m_values[2];
				curPos[1] += m_values[3];
				break;
			case QuadraticBezierCurveToRel:
				fprintf(stderr, "TODO: %s %i\n", __FILE__, __LINE__);
				break;
			case SmoothQuadraticBezierCurveToRel:
				fprintf(stderr, "TODO: %s %i\n", __FILE__, __LINE__);
				break;
			case EllipticalArcToRel:
				fprintf(stderr, "TODO: %s %i\n", __FILE__, __LINE__);
				break;
			case ClosePath:
			case AltClosePath:
				// TODO: actually this means to draw a line back to the first point in the path
				curPos[0] = pathStartPos[0]; curPos[1] = pathStartPos[1];
				//HPDF_Page_LineTo(page, curPos[0], pageHeight - curPos[1]);
				/*
				if (fill)
					HPDF_Page_FillStroke(page);
				else
					HPDF_Page_Stroke(page);
				*/
				break;
			default:
				break;
		}
	}	

	PathOperation  m_type;
	double         m_values[7];
};


class DocSVG : public TiXmlVisitor
{
public:
	DocSVG(double scale);
	virtual ~DocSVG();
	/// Visit a document.
	virtual bool VisitEnter( const TiXmlDocument& doc );
	/// Visit a document.
	virtual bool VisitExit( const TiXmlDocument& doc );
	/// Visit a declaration
	virtual bool Visit( const TiXmlDeclaration& declaration );
	/// Visit a stylesheet reference
	virtual bool Visit( const TiXmlStylesheetReference& stylesheet );
	/// Visit a comment node
	virtual bool Visit( const TiXmlComment& comment );
	/// Visit an unknow node
	virtual bool Visit( const TiXmlUnknown& unknown );
	
	/// Visit an element.
	virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute );
	/// Visit an element.
	virtual bool VisitExit( const TiXmlElement& element );
	/// Visit a text node
	virtual bool Visit( const TiXmlText& text );

	void DumpOperations()
	{
		for (size_t p = 0; p < paths.size(); ++p)
			for (size_t i = 0; i < paths[p].operations.size(); i++)
				paths[p].operations[i].dump();
	}

	void WriteTo(DocOutputPage* outputPage)
	{
		float height = outputPage->height();
		HPDF_Page page = outputPage->page();
    float pageHeight = HPDF_Page_GetHeight(page);
		float curPos[2] = { 10.0, 10.0 };
		//fprintf(stderr, "doc wxh: %f x %f\n", HPDF_Page_GetWidth(page), HPDF_Page_GetHeight(page));
/*	
		HPDF_Page_Rectangle(page, x1, pageHeight - y1 - h, w, h);
		HPDF_Page_Circle(page, x, pageHeight - y, radius);
*/		
    HPDF_Page_EndText(page);

		for (size_t p = 0; p < paths.size(); ++p)
		{
			uint32_t fCol = paths[p].style.fillColor;
			uint32_t sCol = paths[p].style.strokeColor;
			float sWidth  = paths[p].style.strokeWidth;
			float alpha   = paths[p].style.opacity;
			float penA    = alpha * paths[p].style.strokeOpacity;
			float fillA   = alpha * paths[p].style.fillOpacity;
			std::vector<SVGOperation> operations = paths[p].operations;
    
      if (!fCol)
        fCol = 0x123456;
    
      HPDF_Page_GSave(page);

      //outputPage->setAlphaPenFill(penA, fillA);
      outputPage->setPenWidth(1.0);// sWidth * m_scale);
      outputPage->setFillColor(fCol);
      outputPage->setPenColor(sCol);

			HPDF_Page_MoveTo(page, 0.0, height);

			float pathStartPos[2];
			pathStartPos[0] = curPos[0];
			pathStartPos[1] = curPos[1];
			SVGOperation::PathOperation prevOp = SVGOperation::BadOperation;
			for (size_t i = 0; i < operations.size(); i++) {
				SVGOperation op = operations[i];
				//op.scale(m_scale);
				op.scale(2.82);
				op.output(page, curPos, false, prevOp, pathStartPos);
				if (prevOp == SVGOperation::BeginPathOperation) {
					pathStartPos[0] = curPos[0];
					pathStartPos[1] = curPos[1];
				}
				prevOp = op.m_type;
			}

      /*
			if (fCol)
				HPDF_Page_ClosePathFillStroke(page);
			else
				HPDF_Page_Stroke(page);

      HPDF_Page_EndPath(page);
				*/

      //outputPage->setPenWidth(1.0);// sWidth * m_scale);
      //HPDF_Page_Rectangle(page, 0, 0, 100, 100);
      HPDF_Page_Clip(page);
      
      HPDF_Page_EndPath(page);
      //HPDF_Page_Eoclip(page);
      
			
      std::string gradientStr = paths[p].style.fillString;
      gradientStr = gradientStr.c_str() + 5;
      gradientStr = gradientStr.substr(0, gradientStr.size()-1);

      printf("looking for gradient: -%s-\n", gradientStr.c_str());
      if (gradients.count(gradientStr))
      {
        printf("found gradient: -%s-\n", gradientStr.c_str());
	      const SVGGradient& gradient = gradients[gradientStr];
        if (gradient.stops.size() >= 2)
        {
          uint32_t col1 = gradient.stops[0].color;
          uint32_t col2 = gradient.stops[1].color;
          float a1 = gradient.stops[0].opacity;
          float a2 = gradient.stops[1].opacity;

          std::vector<HPDF_GradientStop> stops;
          for (const auto &stop : gradient.stops)
          {
            stops.emplace_back(HPDF_GradientStop{ stop.color, stop.opacity, stop.offset });
          }

          outputPage->setGradient(stops, gradient.x1 * 2.82, pageHeight - gradient.y1 * 2.82,
                                         gradient.x2 * 2.82, pageHeight - gradient.y2 * 2.82);

          /*
          outputPage->setGradient(col1, a1, gradient.x1 * 2.82, pageHeight - gradient.y1 * 2.82,
                                  col2, a2, gradient.x2 * 2.82, pageHeight - gradient.y2 * 2.82);
          */
        }
        else
        {
          printf("not enough stops for gradient: -%s-\n", gradientStr.c_str());
        }
      }
      
 /* 
      HPDF_Page_BeginText(page);
      HPDF_Page_MoveTextPos (page, 50, 50);
      HPDF_Page_SetTextLeading (page, 12);
      HPDF_Page_ShowText (page, "Clip Clip Clip Clip Clip Clipi Clip Clip Clip");
      HPDF_Page_EndText(page);

*/
      HPDF_Page_GRestore(page);

/*
HPDF_Page_Stroke  (HPDF_Page  page)
HPDF_Page_ClosePathStroke  (HPDF_Page  page)

HPDF_Page_Fill  (HPDF_Page  page)
HPDF_Page_Eofill  (HPDF_Page  page)          // Even-Odd Fill

HPDF_Page_FillStroke  (HPDF_Page  page)
HPDF_Page_EofillStroke  (HPDF_Page  page)

HPDF_Page_ClosePathFillStroke  (HPDF_Page  page)
HPDF_Page_ClosePathEofillStroke  (HPDF_Page  page)

HPDF_Page_EndPath  (HPDF_Page  page)
*/

		}

    /*
    //
    // Example of drawing triangles with vertex coloring
    //
    float pnts[]    = { 0.0, 0.0,   100.0, 100.0,   0.0, 100.0 };
    uint32_t cols[] = { 0x8092A4C6,     0x80C6A492,      0x80A4C692  };
    outputPage->drawGradient(pnts, 3, (unsigned char*)cols, 4);
    */

		HPDF_Page_BeginText(page);
	}

private:
	struct SVGStyle
  {
    float         opacity;

    float         fillOpacity;
    uint32_t		  fillColor;
    std::string   fillString; // url/gradient etc

    float         strokeOpacity;
    uint32_t		  strokeColor;
		float		  	  strokeWidth;
		uint32_t		  strokeLineCap;
		uint32_t		  strokeLineJoin;
	};

  struct SVGPath
  {
		std::vector<SVGOperation> operations;
		SVGStyle                  style;
	};

  struct SVGColorStop
  {
    float      offset;
    uint32_t   color;
    float      opacity;
  };

  struct SVGGradient
  {
    std::string               name;
		std::vector<SVGColorStop> stops;
    float                     x1, y1, x2, y2;
	};

	static SVGStyle parseStyle(const char* a_str);
  static uint32_t parseLineCap(const std::string& s);
  static uint32_t parseLineJoin(const std::string& s);
	static uint32_t parseColor(const char* a_str);
	
  static std::vector<SVGOperation> ParsePath(const char* a_pathData)
	{
		std::vector<SVGOperation> operations;
		SVGOperation currentOp;
		currentOp.m_type = SVGOperation::BeginPathOperation;
		operations.push_back(currentOp);
		currentOp.m_type = SVGOperation::MoveToAbs;
		int val = 0;
		const char* pathPtr = a_pathData;
		while (*pathPtr) {
			char* end = 0;
			double v = strtod(pathPtr, &end);
			if ( end != pathPtr ) {
				currentOp.m_values[val] = v;
				val++;
				if (val == currentOp.getArgCount()) {
					operations.push_back(currentOp);
					// The first position in a move to op is where to go to, but after that it
					// is where to draw a line to
					if ( currentOp.m_type == SVGOperation::MoveToAbs )
						currentOp.m_type = SVGOperation::LineToAbs;
					if ( currentOp.m_type == SVGOperation::MoveToRel )
						currentOp.m_type = SVGOperation::LineToRel;
					val = 0;
				}
				pathPtr = end;
			} else {
				if (SVGOperation::getOperationForChar(*pathPtr) != SVGOperation::BadOperation)
				{
					if (val != 0) {
						fprintf(stderr, "Broken SVG path data, expecting more numbers, got: -%c-\n", *pathPtr);
						break;
					}
					if (currentOp.getArgCount() == 0) // eg: ClosePath
						operations.push_back(currentOp);
					currentOp.m_type = SVGOperation::getOperationForChar(*pathPtr);
				}
				pathPtr++;
			}
		}
		if (val == currentOp.getArgCount()) {
			operations.push_back(currentOp);
		} else if (val) {
			fprintf(stderr, "Broken SVG path data, expecting more numbers\n");
		}
		currentOp.m_type = SVGOperation::EndPathOperation;
		operations.push_back(currentOp);
		return operations;
	}

  SVGGradient*                       currentGradient;
	std::map<std::string, SVGGradient> gradients;
	std::vector<SVGPath>               paths;
	double m_scale;
};


#endif // DOC_SVG_H