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.

536 lines
16KB

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