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

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