Newer
Older
Import / applications / MakePDF / DocSVG.cpp
#include "DocSVG.h"
#include "Util.h"


const char SVGOperation::PathOperationChar[] =
{
	'M', 'L', 'H', 'V', 'C', 'S', 'Q', 'T', 'A', 'Z',
	'm', 'l', 'h', 'v', 'c', 's', 'q', 't', 'a', 'z',   ' ', ' ', ' '
};


const int SVGOperation::PathOperationArgs[] =
{
	2,   2,   1,   1,   6,   4,   4,   2,   7,   0,
	2,   2,   1,   1,   6,   4,   4,   2,   7,   0,     0,  0,  0
};


void SVGOperation::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;
  }
}


DocSVG::DocSVG(double a_scale) : m_scale(a_scale)
{
  currentGradient = nullptr;
}


DocSVG::~DocSVG()
{
}


/// Visit a document.
bool DocSVG::VisitEnter( const TiXmlDocument& /* doc */ )
{
	return true;
}


/// Visit a document.
bool DocSVG::VisitExit( const TiXmlDocument& /* doc */ )
{
	return true;
}


/// Visit a declaration
bool DocSVG::Visit( const TiXmlDeclaration& /* declaration */ )
{
	return true;
}


/// Visit a stylesheet reference
bool DocSVG::Visit( const TiXmlStylesheetReference& /* stylesheet */ )
{
	return true;
}


/// Visit a comment node
bool DocSVG::Visit( const TiXmlComment& /* comment */ )
{
	return true;
}


/// Visit an unknown node
bool DocSVG::Visit( const TiXmlUnknown& /* unknown */ )
{
	return true;
}


// static
DocSVG::SVGStyle DocSVG::parseStyle(const char* a_str)
{
	SVGStyle style = { 0.0, 0.0, 0, "", "", 0.0, 0, 0.0, 0, 0 };
	std::vector<std::string> list = split(a_str, ';');
	for (size_t i = 0; i < list.size(); ++i)
	{
		std::vector<std::string> kvp = split(list[i], ':');
		std::string s = kvp[0];
		if (s == "opacity")              style.opacity        = str2float(kvp[1]);
		else if (s == "fill-opacity")    style.fillOpacity    = str2float(kvp[1]);
    else if (s == "fill")            style.fillString     = kvp[1], style.fillColor = parseColor(kvp[1].c_str());
		else if (s == "stroke-opacity")  style.strokeOpacity  = str2float(kvp[1]);
		else if (s == "stroke")          style.strokeString   = kvp[1], style.strokeColor = parseColor(kvp[1].c_str());
		else if (s == "stroke-width")    style.strokeWidth    = str2float(kvp[1]);
		else if (s == "stroke-linecap")  style.strokeLineCap  = parseLineCap(kvp[1]);
		else if (s == "stroke-linejoin") style.strokeLineJoin = parseLineJoin(kvp[1]);
	}
	return style;
}


// static 
std::vector<SVGOperation> DocSVG::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;
}


// static
uint32_t DocSVG::parseLineCap(const std::string& s)
{
	if (s == "butt")         return 0;
	else if (s == "round")   return 1;
	else if (s == "square")  return 2;
  printf("Unknown line-cap: %s\n", s.c_str());
  return -1;
}


// static
uint32_t DocSVG::parseLineJoin(const std::string& s)
{
	if (s == "round")       return 0;
	else if (s == "bevel")  return 1;
	else if (s == "miter")  return 2;
  printf("Unknown line-join: %s\n", s.c_str());
  return -1;
}


// static
uint32_t DocSVG::parseColor(const char* a_str)
{
	std::string s = a_str;
	if (s == "red")          return 0xffff0000;
	else if (s == "blue")    return 0xff0000ff;
	else if (s == "green")   return 0xff00ff00;
	else if (s == "yellow")  return 0xffffff00;
	else if (s == "purple")  return 0xffe00fe0;
	else if (s == "cyan")    return 0xff00ffff;
	else if (s == "magenta") return 0xffff00ff;
	else if (s == "black")   return 0xff000000;
	else if (s == "white")   return 0xffffffff;
	else if (s == "none")    return 0;
	return 0xff000000 | str2col(s);
}


/// Visit an element.
bool DocSVG::VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute )
{

// Inkscape:
// 	style="fill:#729fcf;stroke:none"
// Spec:
//	fill="yellow" stroke="blue" stroke-width="5"

	SVGPath path;
	path.style = { 0.0, 0.0, 0, "none", "none", 0.0, 0, 0.0, 0, 0 };

	if (strcmp(element.ValueTStr().c_str(), "stop") == 0) {
    if (currentGradient != nullptr)
    {
      SVGColorStop stop;
      const TiXmlAttribute* attrib = firstAttribute;
      while ( attrib ) {
        if (strcmp(attrib->Name(), "offset") == 0) {
          stop.offset = str2float(attrib->Value());
        } else if (strcmp(attrib->Name(), "style") == 0) {
          std::vector<std::string> list = split(attrib->Value(), ';');
          for (size_t i = 0; i < list.size(); ++i)
          {
            std::vector<std::string> kvp = split(list[i], ':');
            std::string s = kvp[0];
            if (s == "stop-color")           stop.color   = parseColor(kvp[1].c_str());
            else if (s == "stop-opacity")    stop.opacity = str2float(kvp[1]);
          }
        }
        attrib = attrib->Next();
      }
      currentGradient->stops.push_back(stop);
    }
  }

	if (strcmp(element.ValueTStr().c_str(), "linearGradient") == 0) {
    SVGGradient gradient;
		const TiXmlAttribute* attrib = firstAttribute;
		while ( attrib ) {
			if (strcmp(attrib->Name(), "id") == 0) {
				gradient.name = attrib->Value();
      } else if (strcmp(attrib->Name(), "xlink:href") == 0) {
        gradient.stops = gradients[attrib->Value() + 1].stops;
      } else if (strcmp(attrib->Name(), "x1") == 0) {
        gradient.x1 = str2float(attrib->Value());
      } else if (strcmp(attrib->Name(), "y1") == 0) {
        gradient.y1 = str2float(attrib->Value());
      } else if (strcmp(attrib->Name(), "x2") == 0) {
        gradient.x2 = str2float(attrib->Value());
      } else if (strcmp(attrib->Name(), "y2") == 0) {
        gradient.y2 = str2float(attrib->Value());
      }
			attrib = attrib->Next();
    }
    gradients[gradient.name] = gradient;
    currentGradient = &gradients[gradient.name];
  }
  
	if (strcmp(element.ValueTStr().c_str(), "path") == 0) {
		const TiXmlAttribute* attrib = firstAttribute;
		while ( attrib ) {
			if (strcmp(attrib->Name(), "d") == 0) {
				path.operations = ParsePath(attrib->Value());
			} else if (strcmp(attrib->Name(), "style") == 0) {
				path.style = parseStyle(attrib->Value());
			} else if (strcmp(attrib->Name(), "fill") == 0) {
				path.style.fillColor = parseColor(attrib->Value());
			} else if (strcmp(attrib->Name(), "stroke") == 0) {
				path.style.strokeColor = parseColor(attrib->Value());
			} else if (strcmp(attrib->Name(), "stroke-width") == 0) {
				path.style.strokeWidth = str2float(attrib->Value());
			}
			attrib = attrib->Next();
		}
	}

  if (strcmp(element.ValueTStr().c_str(), "rect") == 0) {
		const TiXmlAttribute* attrib = firstAttribute;
    float x, y, w, h;
		while ( attrib ) {
			if (strcmp(attrib->Name(), "x") == 0) {
				x = str2float(attrib->Value());
      } else if (strcmp(attrib->Name(), "y") == 0) {
				y = str2float(attrib->Value());
      } else if (strcmp(attrib->Name(), "width") == 0) {
				w = str2float(attrib->Value());
      } else if (strcmp(attrib->Name(), "height") == 0) {
				h = str2float(attrib->Value());
			} else if (strcmp(attrib->Name(), "style") == 0) {
				path.style = parseStyle(attrib->Value());
			} else if (strcmp(attrib->Name(), "fill") == 0) {
				path.style.fillColor = parseColor(attrib->Value());
			} else if (strcmp(attrib->Name(), "stroke") == 0) {
				path.style.strokeColor = parseColor(attrib->Value());
			} else if (strcmp(attrib->Name(), "stroke-width") == 0) {
				path.style.strokeWidth = str2float(attrib->Value());
			}
			attrib = attrib->Next();
		}
		
    SVGOperation currentOp;
		currentOp.m_type = SVGOperation::BeginPathOperation;
		path.operations.push_back(currentOp);

    currentOp.m_type = SVGOperation::MoveToAbs;
    currentOp.m_values[0] = x;
    currentOp.m_values[1] = y;
		path.operations.push_back(currentOp);

    currentOp.m_type = SVGOperation::HorizontalLineToRel;
    currentOp.m_values[0] = w;
		path.operations.push_back(currentOp);
		
    currentOp.m_type = SVGOperation::VerticalLineToRel;
    currentOp.m_values[0] = h;
		path.operations.push_back(currentOp);

    currentOp.m_type = SVGOperation::HorizontalLineToRel;
    currentOp.m_values[0] = -w;
		path.operations.push_back(currentOp);
    
    currentOp.m_type = SVGOperation::ClosePath;
		path.operations.push_back(currentOp);

		currentOp.m_type = SVGOperation::EndPathOperation;
		path.operations.push_back(currentOp);
	}

	paths.push_back(path);

	return true;
}


/// Visit an element.
bool DocSVG::VisitExit( const TiXmlElement& /* element */ )
{
	return true;
}


/// Visit a text node
bool DocSVG::Visit( const TiXmlText& /* text */ )
{
	return true;
}


void DocSVG::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)
  {
    bool strokeEnabled = paths[p].style.strokeString != "none";
    bool fillEnabled = paths[p].style.fillString != "none";

    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;

    // Debugging
    if (!strokeEnabled)
      sCol = 0xFFFFFF;//0xFF00FF00;
    if (!fillEnabled)
      fCol = 0xFF00FF;//0xFFFF0000;
    //if (!fCol)
    //  fCol = 0x123456;
    
    std::string gradientStr = paths[p].style.fillString;
    if (fillEnabled)
    {
      gradientStr = gradientStr.c_str() + 5;
      gradientStr = gradientStr.substr(0, gradientStr.size()-1);
      
      if (!gradients.count(gradientStr)) {
        printf("GRADIENT NOT FOUND: --%s--\n", gradientStr.c_str());
      }

    }

    //if (gradients.count(gradientStr))
    {

    HPDF_Page_GSave(page);

    outputPage->setPenWidth(1.0);// sWidth * m_scale);
    outputPage->setFillColor(fCol);
    outputPage->setPenColor(sCol);
//    outputPage->setAlpha(alpha);
    
    outputPage->setPenWidth(sWidth * 2.82);
 
    /*
    outputPage->setAlphaPenFill(penA, fillA);
    */
    HPDF_ExtGState extState = HPDF_CreateExtGState(outputPage->doc());
    HPDF_ExtGState_SetAlphaStroke(extState, (strokeEnabled) ? penA : 0.0);
    HPDF_ExtGState_SetAlphaFill(extState, (fillEnabled) ? fillA : 0.0);
    // HPDF_ExtGState_SetBlendMode(extState, (HPDF_BlendMode)0);
    HPDF_ExtGState_SetBlendMode(extState, HPDF_BM_NORMAL);
    HPDF_Page_SetExtGState(page, extState);

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


    } // End gradient


    printf("looking for gradient: -%s-\n", gradientStr.c_str());
    if (gradients.count(gradientStr))
    {
      printf("found gradient: -%s-\n", gradientStr.c_str());

      HPDF_Page_Clip(page);

      if (strokeEnabled) // if (sCol)
        HPDF_Page_Stroke(page);
      else
        HPDF_Page_EndPath(page);

      //HPDF_Page_Eoclip(page);

      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);
           */
        HPDF_Page_GRestore(page);
      }
      else
      {
        printf("not enough stops for gradient: -%s-\n", gradientStr.c_str());
      }
    }
    else
    {
      if (fillEnabled && strokeEnabled)
        HPDF_Page_ClosePathFillStroke(page);
      else if (strokeEnabled)
        HPDF_Page_Stroke(page);
      else if (fillEnabled)
        HPDF_Page_Fill(page);
      else
        HPDF_Page_EndPath(page);
      HPDF_Page_GRestore(page);

      /*
         if (fCol)
         HPDF_Page_ClosePathFillStroke(page);
         else
         HPDF_Page_Stroke(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);
}