import-svg.cpp 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. #include "import-svg.h"
  2. #include <cstdio>
  3. #include <tinyxml2.h>
  4. #ifdef _WIN32
  5. #pragma warning(disable:4996)
  6. #endif
  7. namespace msdfgen {
  8. #define REQUIRE(cond) { if (!(cond)) return false; }
  9. static bool readNodeType(char &output, const char *&pathDef) {
  10. int shift;
  11. char nodeType;
  12. if (sscanf(pathDef, " %c%n", &nodeType, &shift) == 1 && nodeType != '+' && nodeType != '-' && nodeType != '.' && nodeType != ',' && (nodeType < '0' || nodeType > '9')) {
  13. pathDef += shift;
  14. output = nodeType;
  15. return true;
  16. }
  17. return false;
  18. }
  19. static bool readCoord(Point2 &output, const char *&pathDef) {
  20. int shift;
  21. double x, y;
  22. if (sscanf(pathDef, "%lf%lf%n", &x, &y, &shift) == 2) {
  23. output.x = x;
  24. output.y = y;
  25. pathDef += shift;
  26. return true;
  27. }
  28. if (sscanf(pathDef, "%lf,%lf%n", &x, &y, &shift) == 2) {
  29. output.x = x;
  30. output.y = y;
  31. pathDef += shift;
  32. return true;
  33. }
  34. return false;
  35. }
  36. static bool readDouble(double &output, const char *&pathDef) {
  37. int shift;
  38. double v;
  39. if (sscanf(pathDef, "%lf%n", &v, &shift) == 1) {
  40. pathDef += shift;
  41. output = v;
  42. return true;
  43. }
  44. return false;
  45. }
  46. static void consumeOptionalComma(const char *&pathDef) {
  47. while (*pathDef == ' ')
  48. ++pathDef;
  49. if (*pathDef == ',')
  50. ++pathDef;
  51. }
  52. static bool buildFromPath(Shape &shape, const char *pathDef) {
  53. char nodeType;
  54. Point2 prevNode(0, 0);
  55. while (readNodeType(nodeType, pathDef)) {
  56. Contour &contour = shape.addContour();
  57. bool contourStart = true;
  58. Point2 startPoint;
  59. Point2 controlPoint[2];
  60. Point2 node;
  61. while (true) {
  62. switch (nodeType) {
  63. case 'M': case 'm':
  64. REQUIRE(contourStart);
  65. REQUIRE(readCoord(node, pathDef));
  66. if (nodeType == 'm')
  67. node += prevNode;
  68. startPoint = node;
  69. --nodeType; // to 'L' or 'l'
  70. break;
  71. case 'Z': case 'z':
  72. if (prevNode != startPoint)
  73. contour.addEdge(new LinearSegment(prevNode, startPoint));
  74. prevNode = startPoint;
  75. goto NEXT_CONTOUR;
  76. case 'L': case 'l':
  77. REQUIRE(readCoord(node, pathDef));
  78. if (nodeType == 'l')
  79. node += prevNode;
  80. contour.addEdge(new LinearSegment(prevNode, node));
  81. break;
  82. case 'H': case 'h':
  83. REQUIRE(readDouble(node.x, pathDef));
  84. if (nodeType == 'h')
  85. node.x += prevNode.x;
  86. contour.addEdge(new LinearSegment(prevNode, node));
  87. break;
  88. case 'V': case 'v':
  89. REQUIRE(readDouble(node.y, pathDef));
  90. if (nodeType == 'v')
  91. node.y += prevNode.y;
  92. contour.addEdge(new LinearSegment(prevNode, node));
  93. break;
  94. case 'Q': case 'q':
  95. REQUIRE(readCoord(controlPoint[0], pathDef));
  96. consumeOptionalComma(pathDef);
  97. REQUIRE(readCoord(node, pathDef));
  98. if (nodeType == 'q') {
  99. controlPoint[0] += prevNode;
  100. node += prevNode;
  101. }
  102. contour.addEdge(new QuadraticSegment(prevNode, controlPoint[0], node));
  103. break;
  104. // TODO T, t
  105. case 'C': case 'c':
  106. REQUIRE(readCoord(controlPoint[0], pathDef));
  107. consumeOptionalComma(pathDef);
  108. REQUIRE(readCoord(controlPoint[1], pathDef));
  109. consumeOptionalComma(pathDef);
  110. REQUIRE(readCoord(node, pathDef));
  111. if (nodeType == 'c') {
  112. controlPoint[0] += prevNode;
  113. controlPoint[1] += prevNode;
  114. node += prevNode;
  115. }
  116. contour.addEdge(new CubicSegment(prevNode, controlPoint[0], controlPoint[1], node));
  117. break;
  118. case 'S': case 's':
  119. controlPoint[0] = node+node-controlPoint[1];
  120. REQUIRE(readCoord(controlPoint[1], pathDef));
  121. consumeOptionalComma(pathDef);
  122. REQUIRE(readCoord(node, pathDef));
  123. if (nodeType == 's') {
  124. controlPoint[1] += prevNode;
  125. node += prevNode;
  126. }
  127. contour.addEdge(new CubicSegment(prevNode, controlPoint[0], controlPoint[1], node));
  128. break;
  129. // TODO A, a
  130. default:
  131. REQUIRE(false);
  132. }
  133. contourStart &= nodeType == 'M' || nodeType == 'm';
  134. prevNode = node;
  135. readNodeType(nodeType, pathDef);
  136. }
  137. NEXT_CONTOUR:;
  138. }
  139. return true;
  140. }
  141. bool loadSvgShape(Shape &output, const char *filename, Vector2 *dimensions) {
  142. tinyxml2::XMLDocument doc;
  143. if (doc.LoadFile(filename))
  144. return false;
  145. tinyxml2::XMLElement *root = doc.FirstChildElement("svg");
  146. if (!root)
  147. return false;
  148. tinyxml2::XMLElement *path = root->FirstChildElement("path");
  149. if (!path) {
  150. tinyxml2::XMLElement *g = root->FirstChildElement("g");
  151. if (g)
  152. path = g->FirstChildElement("path");
  153. }
  154. if (!path)
  155. return false;
  156. const char *pd = path->Attribute("d");
  157. if (!pd)
  158. return false;
  159. output.contours.clear();
  160. output.inverseYAxis = true;
  161. if (dimensions) {
  162. dimensions->x = root->DoubleAttribute("width");
  163. dimensions->y = root->DoubleAttribute("height");
  164. }
  165. return buildFromPath(output, pd);
  166. }
  167. }