The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

532 lines
16KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. // this will throw an assertion if you try to draw something that's not
  21. // possible in postscript
  22. #define WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS 0
  23. //==============================================================================
  24. #if JUCE_DEBUG && WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS
  25. #define notPossibleInPostscriptAssert jassertfalse
  26. #else
  27. #define notPossibleInPostscriptAssert
  28. #endif
  29. //==============================================================================
  30. LowLevelGraphicsPostScriptRenderer::LowLevelGraphicsPostScriptRenderer (OutputStream& resultingPostScript,
  31. const String& documentTitle,
  32. const int totalWidth_,
  33. const int totalHeight_)
  34. : out (resultingPostScript),
  35. totalWidth (totalWidth_),
  36. totalHeight (totalHeight_),
  37. needToClip (true)
  38. {
  39. stateStack.add (new SavedState());
  40. stateStack.getLast()->clip = Rectangle<int> (totalWidth_, totalHeight_);
  41. const float scale = jmin ((520.0f / (float) totalWidth_), (750.0f / (float) totalHeight));
  42. out << "%!PS-Adobe-3.0 EPSF-3.0"
  43. "\n%%BoundingBox: 0 0 600 824"
  44. "\n%%Pages: 0"
  45. "\n%%Creator: Raw Material Software Limited - JUCE"
  46. "\n%%Title: " << documentTitle <<
  47. "\n%%CreationDate: none"
  48. "\n%%LanguageLevel: 2"
  49. "\n%%EndComments"
  50. "\n%%BeginProlog"
  51. "\n%%BeginResource: JRes"
  52. "\n/bd {bind def} bind def"
  53. "\n/c {setrgbcolor} bd"
  54. "\n/m {moveto} bd"
  55. "\n/l {lineto} bd"
  56. "\n/rl {rlineto} bd"
  57. "\n/ct {curveto} bd"
  58. "\n/cp {closepath} bd"
  59. "\n/pr {3 index 3 index moveto 1 index 0 rlineto 0 1 index rlineto pop neg 0 rlineto pop pop closepath} bd"
  60. "\n/doclip {initclip newpath} bd"
  61. "\n/endclip {clip newpath} bd"
  62. "\n%%EndResource"
  63. "\n%%EndProlog"
  64. "\n%%BeginSetup"
  65. "\n%%EndSetup"
  66. "\n%%Page: 1 1"
  67. "\n%%BeginPageSetup"
  68. "\n%%EndPageSetup\n\n"
  69. << "40 800 translate\n"
  70. << scale << ' ' << scale << " scale\n\n";
  71. }
  72. //==============================================================================
  73. bool LowLevelGraphicsPostScriptRenderer::isVectorDevice() const
  74. {
  75. return true;
  76. }
  77. void LowLevelGraphicsPostScriptRenderer::setOrigin (Point<int> o)
  78. {
  79. if (! o.isOrigin())
  80. {
  81. stateStack.getLast()->xOffset += o.x;
  82. stateStack.getLast()->yOffset += o.y;
  83. needToClip = true;
  84. }
  85. }
  86. void LowLevelGraphicsPostScriptRenderer::addTransform (const AffineTransform& /*transform*/)
  87. {
  88. //xxx
  89. jassertfalse;
  90. }
  91. float LowLevelGraphicsPostScriptRenderer::getPhysicalPixelScaleFactor() { return 1.0f; }
  92. bool LowLevelGraphicsPostScriptRenderer::clipToRectangle (const Rectangle<int>& r)
  93. {
  94. needToClip = true;
  95. return stateStack.getLast()->clip.clipTo (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset));
  96. }
  97. bool LowLevelGraphicsPostScriptRenderer::clipToRectangleList (const RectangleList<int>& clipRegion)
  98. {
  99. needToClip = true;
  100. return stateStack.getLast()->clip.clipTo (clipRegion);
  101. }
  102. void LowLevelGraphicsPostScriptRenderer::excludeClipRectangle (const Rectangle<int>& r)
  103. {
  104. needToClip = true;
  105. stateStack.getLast()->clip.subtract (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset));
  106. }
  107. void LowLevelGraphicsPostScriptRenderer::clipToPath (const Path& path, const AffineTransform& transform)
  108. {
  109. writeClip();
  110. Path p (path);
  111. p.applyTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset));
  112. writePath (p);
  113. out << "clip\n";
  114. }
  115. void LowLevelGraphicsPostScriptRenderer::clipToImageAlpha (const Image& /*sourceImage*/, const AffineTransform& /*transform*/)
  116. {
  117. needToClip = true;
  118. jassertfalse; // xxx
  119. }
  120. bool LowLevelGraphicsPostScriptRenderer::clipRegionIntersects (const Rectangle<int>& r)
  121. {
  122. return stateStack.getLast()->clip.intersectsRectangle (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset));
  123. }
  124. Rectangle<int> LowLevelGraphicsPostScriptRenderer::getClipBounds() const
  125. {
  126. return stateStack.getLast()->clip.getBounds().translated (-stateStack.getLast()->xOffset,
  127. -stateStack.getLast()->yOffset);
  128. }
  129. bool LowLevelGraphicsPostScriptRenderer::isClipEmpty() const
  130. {
  131. return stateStack.getLast()->clip.isEmpty();
  132. }
  133. //==============================================================================
  134. LowLevelGraphicsPostScriptRenderer::SavedState::SavedState()
  135. : xOffset (0),
  136. yOffset (0)
  137. {
  138. }
  139. void LowLevelGraphicsPostScriptRenderer::saveState()
  140. {
  141. stateStack.add (new SavedState (*stateStack.getLast()));
  142. }
  143. void LowLevelGraphicsPostScriptRenderer::restoreState()
  144. {
  145. jassert (stateStack.size() > 0);
  146. if (stateStack.size() > 0)
  147. stateStack.removeLast();
  148. }
  149. void LowLevelGraphicsPostScriptRenderer::beginTransparencyLayer (float)
  150. {
  151. }
  152. void LowLevelGraphicsPostScriptRenderer::endTransparencyLayer()
  153. {
  154. }
  155. //==============================================================================
  156. void LowLevelGraphicsPostScriptRenderer::writeClip()
  157. {
  158. if (needToClip)
  159. {
  160. needToClip = false;
  161. out << "doclip ";
  162. int itemsOnLine = 0;
  163. for (auto& i : stateStack.getLast()->clip)
  164. {
  165. if (++itemsOnLine == 6)
  166. {
  167. itemsOnLine = 0;
  168. out << '\n';
  169. }
  170. out << i.getX() << ' ' << -i.getY() << ' '
  171. << i.getWidth() << ' ' << -i.getHeight() << " pr ";
  172. }
  173. out << "endclip\n";
  174. }
  175. }
  176. void LowLevelGraphicsPostScriptRenderer::writeColour (Colour colour)
  177. {
  178. Colour c (Colours::white.overlaidWith (colour));
  179. if (lastColour != c)
  180. {
  181. lastColour = c;
  182. out << String (c.getFloatRed(), 3) << ' '
  183. << String (c.getFloatGreen(), 3) << ' '
  184. << String (c.getFloatBlue(), 3) << " c\n";
  185. }
  186. }
  187. void LowLevelGraphicsPostScriptRenderer::writeXY (const float x, const float y) const
  188. {
  189. out << String (x, 2) << ' '
  190. << String (-y, 2) << ' ';
  191. }
  192. void LowLevelGraphicsPostScriptRenderer::writePath (const Path& path) const
  193. {
  194. out << "newpath ";
  195. float lastX = 0.0f;
  196. float lastY = 0.0f;
  197. int itemsOnLine = 0;
  198. Path::Iterator i (path);
  199. while (i.next())
  200. {
  201. if (++itemsOnLine == 4)
  202. {
  203. itemsOnLine = 0;
  204. out << '\n';
  205. }
  206. switch (i.elementType)
  207. {
  208. case Path::Iterator::startNewSubPath:
  209. writeXY (i.x1, i.y1);
  210. lastX = i.x1;
  211. lastY = i.y1;
  212. out << "m ";
  213. break;
  214. case Path::Iterator::lineTo:
  215. writeXY (i.x1, i.y1);
  216. lastX = i.x1;
  217. lastY = i.y1;
  218. out << "l ";
  219. break;
  220. case Path::Iterator::quadraticTo:
  221. {
  222. const float cp1x = lastX + (i.x1 - lastX) * 2.0f / 3.0f;
  223. const float cp1y = lastY + (i.y1 - lastY) * 2.0f / 3.0f;
  224. const float cp2x = cp1x + (i.x2 - lastX) / 3.0f;
  225. const float cp2y = cp1y + (i.y2 - lastY) / 3.0f;
  226. writeXY (cp1x, cp1y);
  227. writeXY (cp2x, cp2y);
  228. writeXY (i.x2, i.y2);
  229. out << "ct ";
  230. lastX = i.x2;
  231. lastY = i.y2;
  232. }
  233. break;
  234. case Path::Iterator::cubicTo:
  235. writeXY (i.x1, i.y1);
  236. writeXY (i.x2, i.y2);
  237. writeXY (i.x3, i.y3);
  238. out << "ct ";
  239. lastX = i.x3;
  240. lastY = i.y3;
  241. break;
  242. case Path::Iterator::closePath:
  243. out << "cp ";
  244. break;
  245. default:
  246. jassertfalse;
  247. break;
  248. }
  249. }
  250. out << '\n';
  251. }
  252. void LowLevelGraphicsPostScriptRenderer::writeTransform (const AffineTransform& trans) const
  253. {
  254. out << "[ "
  255. << trans.mat00 << ' '
  256. << trans.mat10 << ' '
  257. << trans.mat01 << ' '
  258. << trans.mat11 << ' '
  259. << trans.mat02 << ' '
  260. << trans.mat12 << " ] concat ";
  261. }
  262. //==============================================================================
  263. void LowLevelGraphicsPostScriptRenderer::setFill (const FillType& fillType)
  264. {
  265. stateStack.getLast()->fillType = fillType;
  266. }
  267. void LowLevelGraphicsPostScriptRenderer::setOpacity (float /*opacity*/)
  268. {
  269. }
  270. void LowLevelGraphicsPostScriptRenderer::setInterpolationQuality (Graphics::ResamplingQuality /*quality*/)
  271. {
  272. }
  273. //==============================================================================
  274. void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle<int>& r, const bool /*replaceExistingContents*/)
  275. {
  276. fillRect (r.toFloat());
  277. }
  278. void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle<float>& r)
  279. {
  280. if (stateStack.getLast()->fillType.isColour())
  281. {
  282. writeClip();
  283. writeColour (stateStack.getLast()->fillType.colour);
  284. auto r2 = r.translated ((float) stateStack.getLast()->xOffset,
  285. (float) stateStack.getLast()->yOffset);
  286. out << r2.getX() << ' ' << -r2.getBottom() << ' ' << r2.getWidth() << ' ' << r2.getHeight() << " rectfill\n";
  287. }
  288. else
  289. {
  290. Path p;
  291. p.addRectangle (r);
  292. fillPath (p, AffineTransform());
  293. }
  294. }
  295. void LowLevelGraphicsPostScriptRenderer::fillRectList (const RectangleList<float>& list)
  296. {
  297. fillPath (list.toPath(), AffineTransform());
  298. }
  299. //==============================================================================
  300. void LowLevelGraphicsPostScriptRenderer::fillPath (const Path& path, const AffineTransform& t)
  301. {
  302. if (stateStack.getLast()->fillType.isColour())
  303. {
  304. writeClip();
  305. Path p (path);
  306. p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset,
  307. (float) stateStack.getLast()->yOffset));
  308. writePath (p);
  309. writeColour (stateStack.getLast()->fillType.colour);
  310. out << "fill\n";
  311. }
  312. else if (stateStack.getLast()->fillType.isGradient())
  313. {
  314. // this doesn't work correctly yet - it could be improved to handle solid gradients, but
  315. // postscript can't do semi-transparent ones.
  316. notPossibleInPostscriptAssert; // you can disable this warning by setting the WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS flag at the top of this file
  317. writeClip();
  318. out << "gsave ";
  319. {
  320. Path p (path);
  321. p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset));
  322. writePath (p);
  323. out << "clip\n";
  324. }
  325. auto bounds = stateStack.getLast()->clip.getBounds();
  326. // ideally this would draw lots of lines or ellipses to approximate the gradient, but for the
  327. // time-being, this just fills it with the average colour..
  328. writeColour (stateStack.getLast()->fillType.gradient->getColourAtPosition (0.5f));
  329. out << bounds.getX() << ' ' << -bounds.getBottom() << ' ' << bounds.getWidth() << ' ' << bounds.getHeight() << " rectfill\n";
  330. out << "grestore\n";
  331. }
  332. }
  333. //==============================================================================
  334. void LowLevelGraphicsPostScriptRenderer::writeImage (const Image& im,
  335. const int sx, const int sy,
  336. const int maxW, const int maxH) const
  337. {
  338. out << "{<\n";
  339. const int w = jmin (maxW, im.getWidth());
  340. const int h = jmin (maxH, im.getHeight());
  341. int charsOnLine = 0;
  342. const Image::BitmapData srcData (im, 0, 0, w, h);
  343. Colour pixel;
  344. for (int y = h; --y >= 0;)
  345. {
  346. for (int x = 0; x < w; ++x)
  347. {
  348. const uint8* pixelData = srcData.getPixelPointer (x, y);
  349. if (x >= sx && y >= sy)
  350. {
  351. if (im.isARGB())
  352. {
  353. PixelARGB p (*(const PixelARGB*) pixelData);
  354. p.unpremultiply();
  355. pixel = Colours::white.overlaidWith (Colour (p));
  356. }
  357. else if (im.isRGB())
  358. {
  359. pixel = Colour (*((const PixelRGB*) pixelData));
  360. }
  361. else
  362. {
  363. pixel = Colour ((uint8) 0, (uint8) 0, (uint8) 0, *pixelData);
  364. }
  365. }
  366. else
  367. {
  368. pixel = Colours::transparentWhite;
  369. }
  370. const uint8 pixelValues[3] = { pixel.getRed(), pixel.getGreen(), pixel.getBlue() };
  371. out << String::toHexString (pixelValues, 3, 0);
  372. charsOnLine += 3;
  373. if (charsOnLine > 100)
  374. {
  375. out << '\n';
  376. charsOnLine = 0;
  377. }
  378. }
  379. }
  380. out << "\n>}\n";
  381. }
  382. void LowLevelGraphicsPostScriptRenderer::drawImage (const Image& sourceImage, const AffineTransform& transform)
  383. {
  384. const int w = sourceImage.getWidth();
  385. const int h = sourceImage.getHeight();
  386. writeClip();
  387. out << "gsave ";
  388. writeTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset)
  389. .scaled (1.0f, -1.0f));
  390. RectangleList<int> imageClip;
  391. sourceImage.createSolidAreaMask (imageClip, 0.5f);
  392. out << "newpath ";
  393. int itemsOnLine = 0;
  394. for (auto& i : imageClip)
  395. {
  396. if (++itemsOnLine == 6)
  397. {
  398. out << '\n';
  399. itemsOnLine = 0;
  400. }
  401. out << i.getX() << ' ' << i.getY() << ' ' << i.getWidth() << ' ' << i.getHeight() << " pr ";
  402. }
  403. out << " clip newpath\n";
  404. out << w << ' ' << h << " scale\n";
  405. out << w << ' ' << h << " 8 [" << w << " 0 0 -" << h << ' ' << (int) 0 << ' ' << h << " ]\n";
  406. writeImage (sourceImage, 0, 0, w, h);
  407. out << "false 3 colorimage grestore\n";
  408. needToClip = true;
  409. }
  410. //==============================================================================
  411. void LowLevelGraphicsPostScriptRenderer::drawLine (const Line <float>& line)
  412. {
  413. Path p;
  414. p.addLineSegment (line, 1.0f);
  415. fillPath (p, AffineTransform());
  416. }
  417. //==============================================================================
  418. void LowLevelGraphicsPostScriptRenderer::setFont (const Font& newFont)
  419. {
  420. stateStack.getLast()->font = newFont;
  421. }
  422. const Font& LowLevelGraphicsPostScriptRenderer::getFont()
  423. {
  424. return stateStack.getLast()->font;
  425. }
  426. void LowLevelGraphicsPostScriptRenderer::drawGlyph (int glyphNumber, const AffineTransform& transform)
  427. {
  428. Path p;
  429. Font& font = stateStack.getLast()->font;
  430. font.getTypefacePtr()->getOutlineForGlyph (glyphNumber, p);
  431. fillPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()).followedBy (transform));
  432. }
  433. } // namespace juce