Audio plugin host https://kx.studio/carla
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.

juce_LowLevelGraphicsPostScriptRenderer.cpp 16KB

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