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