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.

538 lines
17KB

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