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.

535 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() { return 1.0f; }
  92. float LowLevelGraphicsPostScriptRenderer::getTargetDeviceScaleFactor() { return 1.0f; }
  93. bool LowLevelGraphicsPostScriptRenderer::clipToRectangle (const Rectangle<int>& r)
  94. {
  95. needToClip = true;
  96. return stateStack.getLast()->clip.clipTo (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset));
  97. }
  98. bool LowLevelGraphicsPostScriptRenderer::clipToRectangleList (const RectangleList<int>& clipRegion)
  99. {
  100. needToClip = true;
  101. return stateStack.getLast()->clip.clipTo (clipRegion);
  102. }
  103. void LowLevelGraphicsPostScriptRenderer::excludeClipRectangle (const Rectangle<int>& r)
  104. {
  105. needToClip = true;
  106. stateStack.getLast()->clip.subtract (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset));
  107. }
  108. void LowLevelGraphicsPostScriptRenderer::clipToPath (const Path& path, const AffineTransform& transform)
  109. {
  110. writeClip();
  111. Path p (path);
  112. p.applyTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset));
  113. writePath (p);
  114. out << "clip\n";
  115. }
  116. void LowLevelGraphicsPostScriptRenderer::clipToImageAlpha (const Image& /*sourceImage*/, const AffineTransform& /*transform*/)
  117. {
  118. needToClip = true;
  119. jassertfalse; // xxx
  120. }
  121. bool LowLevelGraphicsPostScriptRenderer::clipRegionIntersects (const Rectangle<int>& r)
  122. {
  123. return stateStack.getLast()->clip.intersectsRectangle (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset));
  124. }
  125. Rectangle<int> LowLevelGraphicsPostScriptRenderer::getClipBounds() const
  126. {
  127. return stateStack.getLast()->clip.getBounds().translated (-stateStack.getLast()->xOffset,
  128. -stateStack.getLast()->yOffset);
  129. }
  130. bool LowLevelGraphicsPostScriptRenderer::isClipEmpty() const
  131. {
  132. return stateStack.getLast()->clip.isEmpty();
  133. }
  134. //==============================================================================
  135. LowLevelGraphicsPostScriptRenderer::SavedState::SavedState()
  136. : xOffset (0),
  137. yOffset (0)
  138. {
  139. }
  140. LowLevelGraphicsPostScriptRenderer::SavedState::~SavedState()
  141. {
  142. }
  143. void LowLevelGraphicsPostScriptRenderer::saveState()
  144. {
  145. stateStack.add (new SavedState (*stateStack.getLast()));
  146. }
  147. void LowLevelGraphicsPostScriptRenderer::restoreState()
  148. {
  149. jassert (stateStack.size() > 0);
  150. if (stateStack.size() > 0)
  151. stateStack.removeLast();
  152. }
  153. void LowLevelGraphicsPostScriptRenderer::beginTransparencyLayer (float)
  154. {
  155. }
  156. void LowLevelGraphicsPostScriptRenderer::endTransparencyLayer()
  157. {
  158. }
  159. //==============================================================================
  160. void LowLevelGraphicsPostScriptRenderer::writeClip()
  161. {
  162. if (needToClip)
  163. {
  164. needToClip = false;
  165. out << "doclip ";
  166. int itemsOnLine = 0;
  167. for (const Rectangle<int>* i = stateStack.getLast()->clip.begin(), * const e = stateStack.getLast()->clip.end(); i != e; ++i)
  168. {
  169. if (++itemsOnLine == 6)
  170. {
  171. itemsOnLine = 0;
  172. out << '\n';
  173. }
  174. out << i->getX() << ' ' << -i->getY() << ' '
  175. << i->getWidth() << ' ' << -i->getHeight() << " pr ";
  176. }
  177. out << "endclip\n";
  178. }
  179. }
  180. void LowLevelGraphicsPostScriptRenderer::writeColour (const Colour& colour)
  181. {
  182. Colour c (Colours::white.overlaidWith (colour));
  183. if (lastColour != c)
  184. {
  185. lastColour = c;
  186. out << String (c.getFloatRed(), 3) << ' '
  187. << String (c.getFloatGreen(), 3) << ' '
  188. << String (c.getFloatBlue(), 3) << " c\n";
  189. }
  190. }
  191. void LowLevelGraphicsPostScriptRenderer::writeXY (const float x, const float y) const
  192. {
  193. out << String (x, 2) << ' '
  194. << String (-y, 2) << ' ';
  195. }
  196. void LowLevelGraphicsPostScriptRenderer::writePath (const Path& path) const
  197. {
  198. out << "newpath ";
  199. float lastX = 0.0f;
  200. float lastY = 0.0f;
  201. int itemsOnLine = 0;
  202. Path::Iterator i (path);
  203. while (i.next())
  204. {
  205. if (++itemsOnLine == 4)
  206. {
  207. itemsOnLine = 0;
  208. out << '\n';
  209. }
  210. switch (i.elementType)
  211. {
  212. case Path::Iterator::startNewSubPath:
  213. writeXY (i.x1, i.y1);
  214. lastX = i.x1;
  215. lastY = i.y1;
  216. out << "m ";
  217. break;
  218. case Path::Iterator::lineTo:
  219. writeXY (i.x1, i.y1);
  220. lastX = i.x1;
  221. lastY = i.y1;
  222. out << "l ";
  223. break;
  224. case Path::Iterator::quadraticTo:
  225. {
  226. const float cp1x = lastX + (i.x1 - lastX) * 2.0f / 3.0f;
  227. const float cp1y = lastY + (i.y1 - lastY) * 2.0f / 3.0f;
  228. const float cp2x = cp1x + (i.x2 - lastX) / 3.0f;
  229. const float cp2y = cp1y + (i.y2 - lastY) / 3.0f;
  230. writeXY (cp1x, cp1y);
  231. writeXY (cp2x, cp2y);
  232. writeXY (i.x2, i.y2);
  233. out << "ct ";
  234. lastX = i.x2;
  235. lastY = i.y2;
  236. }
  237. break;
  238. case Path::Iterator::cubicTo:
  239. writeXY (i.x1, i.y1);
  240. writeXY (i.x2, i.y2);
  241. writeXY (i.x3, i.y3);
  242. out << "ct ";
  243. lastX = i.x3;
  244. lastY = i.y3;
  245. break;
  246. case Path::Iterator::closePath:
  247. out << "cp ";
  248. break;
  249. default:
  250. jassertfalse;
  251. break;
  252. }
  253. }
  254. out << '\n';
  255. }
  256. void LowLevelGraphicsPostScriptRenderer::writeTransform (const AffineTransform& trans) const
  257. {
  258. out << "[ "
  259. << trans.mat00 << ' '
  260. << trans.mat10 << ' '
  261. << trans.mat01 << ' '
  262. << trans.mat11 << ' '
  263. << trans.mat02 << ' '
  264. << trans.mat12 << " ] concat ";
  265. }
  266. //==============================================================================
  267. void LowLevelGraphicsPostScriptRenderer::setFill (const FillType& fillType)
  268. {
  269. stateStack.getLast()->fillType = fillType;
  270. }
  271. void LowLevelGraphicsPostScriptRenderer::setOpacity (float /*opacity*/)
  272. {
  273. }
  274. void LowLevelGraphicsPostScriptRenderer::setInterpolationQuality (Graphics::ResamplingQuality /*quality*/)
  275. {
  276. }
  277. //==============================================================================
  278. void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle<int>& r, const bool /*replaceExistingContents*/)
  279. {
  280. if (stateStack.getLast()->fillType.isColour())
  281. {
  282. writeClip();
  283. writeColour (stateStack.getLast()->fillType.colour);
  284. Rectangle<int> r2 (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset));
  285. out << r2.getX() << ' ' << -r2.getBottom() << ' ' << r2.getWidth() << ' ' << r2.getHeight() << " rectfill\n";
  286. }
  287. else
  288. {
  289. Path p;
  290. p.addRectangle (r);
  291. fillPath (p, AffineTransform::identity);
  292. }
  293. }
  294. //==============================================================================
  295. void LowLevelGraphicsPostScriptRenderer::fillPath (const Path& path, const AffineTransform& t)
  296. {
  297. if (stateStack.getLast()->fillType.isColour())
  298. {
  299. writeClip();
  300. Path p (path);
  301. p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset,
  302. (float) stateStack.getLast()->yOffset));
  303. writePath (p);
  304. writeColour (stateStack.getLast()->fillType.colour);
  305. out << "fill\n";
  306. }
  307. else if (stateStack.getLast()->fillType.isGradient())
  308. {
  309. // this doesn't work correctly yet - it could be improved to handle solid gradients, but
  310. // postscript can't do semi-transparent ones.
  311. notPossibleInPostscriptAssert // you can disable this warning by setting the WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS flag at the top of this file
  312. writeClip();
  313. out << "gsave ";
  314. {
  315. Path p (path);
  316. p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset));
  317. writePath (p);
  318. out << "clip\n";
  319. }
  320. const Rectangle<int> bounds (stateStack.getLast()->clip.getBounds());
  321. // ideally this would draw lots of lines or ellipses to approximate the gradient, but for the
  322. // time-being, this just fills it with the average colour..
  323. writeColour (stateStack.getLast()->fillType.gradient->getColourAtPosition (0.5f));
  324. out << bounds.getX() << ' ' << -bounds.getBottom() << ' ' << bounds.getWidth() << ' ' << bounds.getHeight() << " rectfill\n";
  325. out << "grestore\n";
  326. }
  327. }
  328. //==============================================================================
  329. void LowLevelGraphicsPostScriptRenderer::writeImage (const Image& im,
  330. const int sx, const int sy,
  331. const int maxW, const int maxH) const
  332. {
  333. out << "{<\n";
  334. const int w = jmin (maxW, im.getWidth());
  335. const int h = jmin (maxH, im.getHeight());
  336. int charsOnLine = 0;
  337. const Image::BitmapData srcData (im, 0, 0, w, h);
  338. Colour pixel;
  339. for (int y = h; --y >= 0;)
  340. {
  341. for (int x = 0; x < w; ++x)
  342. {
  343. const uint8* pixelData = srcData.getPixelPointer (x, y);
  344. if (x >= sx && y >= sy)
  345. {
  346. if (im.isARGB())
  347. {
  348. PixelARGB p (*(const PixelARGB*) pixelData);
  349. p.unpremultiply();
  350. pixel = Colours::white.overlaidWith (Colour (p.getARGB()));
  351. }
  352. else if (im.isRGB())
  353. {
  354. pixel = Colour (((const PixelRGB*) pixelData)->getARGB());
  355. }
  356. else
  357. {
  358. pixel = Colour ((uint8) 0, (uint8) 0, (uint8) 0, *pixelData);
  359. }
  360. }
  361. else
  362. {
  363. pixel = Colours::transparentWhite;
  364. }
  365. const uint8 pixelValues[3] = { pixel.getRed(), pixel.getGreen(), pixel.getBlue() };
  366. out << String::toHexString (pixelValues, 3, 0);
  367. charsOnLine += 3;
  368. if (charsOnLine > 100)
  369. {
  370. out << '\n';
  371. charsOnLine = 0;
  372. }
  373. }
  374. }
  375. out << "\n>}\n";
  376. }
  377. void LowLevelGraphicsPostScriptRenderer::drawImage (const Image& sourceImage, const AffineTransform& transform)
  378. {
  379. const int w = sourceImage.getWidth();
  380. const int h = sourceImage.getHeight();
  381. writeClip();
  382. out << "gsave ";
  383. writeTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset)
  384. .scaled (1.0f, -1.0f));
  385. RectangleList<int> imageClip;
  386. sourceImage.createSolidAreaMask (imageClip, 0.5f);
  387. out << "newpath ";
  388. int itemsOnLine = 0;
  389. for (const Rectangle<int>* i = imageClip.begin(), * const e = imageClip.end(); i != e; ++i)
  390. {
  391. if (++itemsOnLine == 6)
  392. {
  393. out << '\n';
  394. itemsOnLine = 0;
  395. }
  396. out << i->getX() << ' ' << i->getY() << ' ' << i->getWidth() << ' ' << i->getHeight() << " pr ";
  397. }
  398. out << " clip newpath\n";
  399. out << w << ' ' << h << " scale\n";
  400. out << w << ' ' << h << " 8 [" << w << " 0 0 -" << h << ' ' << (int) 0 << ' ' << h << " ]\n";
  401. writeImage (sourceImage, 0, 0, w, h);
  402. out << "false 3 colorimage grestore\n";
  403. needToClip = true;
  404. }
  405. //==============================================================================
  406. void LowLevelGraphicsPostScriptRenderer::drawLine (const Line <float>& line)
  407. {
  408. Path p;
  409. p.addLineSegment (line, 1.0f);
  410. fillPath (p, AffineTransform::identity);
  411. }
  412. void LowLevelGraphicsPostScriptRenderer::drawVerticalLine (const int x, float top, float bottom)
  413. {
  414. drawLine (Line<float> ((float) x, top, (float) x, bottom));
  415. }
  416. void LowLevelGraphicsPostScriptRenderer::drawHorizontalLine (const int y, float left, float right)
  417. {
  418. drawLine (Line<float> (left, (float) y, right, (float) y));
  419. }
  420. //==============================================================================
  421. void LowLevelGraphicsPostScriptRenderer::setFont (const Font& newFont)
  422. {
  423. stateStack.getLast()->font = newFont;
  424. }
  425. const Font& LowLevelGraphicsPostScriptRenderer::getFont()
  426. {
  427. return stateStack.getLast()->font;
  428. }
  429. void LowLevelGraphicsPostScriptRenderer::drawGlyph (int glyphNumber, const AffineTransform& transform)
  430. {
  431. Path p;
  432. Font& font = stateStack.getLast()->font;
  433. font.getTypeface()->getOutlineForGlyph (glyphNumber, p);
  434. fillPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()).followedBy (transform));
  435. }