JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_LowLevelGraphicsPostScriptRenderer.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - 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 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-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
26namespace 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//==============================================================================
41LowLevelGraphicsPostScriptRenderer::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
86//==============================================================================
87bool LowLevelGraphicsPostScriptRenderer::isVectorDevice() const
88{
89 return true;
90}
91
92void LowLevelGraphicsPostScriptRenderer::setOrigin (Point<int> o)
93{
94 if (! o.isOrigin())
95 {
96 stateStack.getLast()->xOffset += o.x;
97 stateStack.getLast()->yOffset += o.y;
98 needToClip = true;
99 }
100}
101
102void LowLevelGraphicsPostScriptRenderer::addTransform (const AffineTransform& /*transform*/)
103{
104 //xxx
106}
107
108float LowLevelGraphicsPostScriptRenderer::getPhysicalPixelScaleFactor() { return 1.0f; }
109
110bool LowLevelGraphicsPostScriptRenderer::clipToRectangle (const Rectangle<int>& r)
111{
112 needToClip = true;
113 return stateStack.getLast()->clip.clipTo (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset));
114}
115
116bool LowLevelGraphicsPostScriptRenderer::clipToRectangleList (const RectangleList<int>& clipRegion)
117{
118 needToClip = true;
119 return stateStack.getLast()->clip.clipTo (clipRegion);
120}
121
122void LowLevelGraphicsPostScriptRenderer::excludeClipRectangle (const Rectangle<int>& r)
123{
124 needToClip = true;
125 stateStack.getLast()->clip.subtract (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset));
126}
127
128void LowLevelGraphicsPostScriptRenderer::clipToPath (const Path& path, const AffineTransform& transform)
129{
130 writeClip();
131
132 Path p (path);
133 p.applyTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset));
134 writePath (p);
135 out << "clip\n";
136}
137
138void LowLevelGraphicsPostScriptRenderer::clipToImageAlpha (const Image& /*sourceImage*/, const AffineTransform& /*transform*/)
139{
140 needToClip = true;
141 jassertfalse; // xxx
142}
143
144bool LowLevelGraphicsPostScriptRenderer::clipRegionIntersects (const Rectangle<int>& r)
145{
146 return stateStack.getLast()->clip.intersectsRectangle (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset));
147}
148
149Rectangle<int> LowLevelGraphicsPostScriptRenderer::getClipBounds() const
150{
151 return stateStack.getLast()->clip.getBounds().translated (-stateStack.getLast()->xOffset,
152 -stateStack.getLast()->yOffset);
153}
154
155bool LowLevelGraphicsPostScriptRenderer::isClipEmpty() const
156{
157 return stateStack.getLast()->clip.isEmpty();
158}
159
160//==============================================================================
161LowLevelGraphicsPostScriptRenderer::SavedState::SavedState()
162 : xOffset (0),
163 yOffset (0)
164{
165}
166
167void LowLevelGraphicsPostScriptRenderer::saveState()
168{
169 stateStack.add (new SavedState (*stateStack.getLast()));
170}
171
172void LowLevelGraphicsPostScriptRenderer::restoreState()
173{
174 jassert (stateStack.size() > 0);
175
176 if (stateStack.size() > 0)
177 stateStack.removeLast();
178}
179
180void LowLevelGraphicsPostScriptRenderer::beginTransparencyLayer (float)
181{
182}
183
184void LowLevelGraphicsPostScriptRenderer::endTransparencyLayer()
185{
186}
187
188//==============================================================================
189void LowLevelGraphicsPostScriptRenderer::writeClip()
190{
191 if (needToClip)
192 {
193 needToClip = false;
194
195 out << "doclip ";
196
197 int itemsOnLine = 0;
198
199 for (auto& i : stateStack.getLast()->clip)
200 {
201 if (++itemsOnLine == 6)
202 {
203 itemsOnLine = 0;
204 out << '\n';
205 }
206
207 out << i.getX() << ' ' << -i.getY() << ' '
208 << i.getWidth() << ' ' << -i.getHeight() << " pr ";
209 }
210
211 out << "endclip\n";
212 }
213}
214
215void LowLevelGraphicsPostScriptRenderer::writeColour (Colour colour)
216{
217 Colour c (Colours::white.overlaidWith (colour));
218
219 if (lastColour != c)
220 {
221 lastColour = c;
222
223 out << String (c.getFloatRed(), 3) << ' '
224 << String (c.getFloatGreen(), 3) << ' '
225 << String (c.getFloatBlue(), 3) << " c\n";
226 }
227}
228
229void LowLevelGraphicsPostScriptRenderer::writeXY (const float x, const float y) const
230{
231 out << String (x, 2) << ' '
232 << String (-y, 2) << ' ';
233}
234
235void LowLevelGraphicsPostScriptRenderer::writePath (const Path& path) const
236{
237 out << "newpath ";
238
239 float lastX = 0.0f;
240 float lastY = 0.0f;
241 int itemsOnLine = 0;
242
243 Path::Iterator i (path);
244
245 while (i.next())
246 {
247 if (++itemsOnLine == 4)
248 {
249 itemsOnLine = 0;
250 out << '\n';
251 }
252
253 switch (i.elementType)
254 {
256 writeXY (i.x1, i.y1);
257 lastX = i.x1;
258 lastY = i.y1;
259 out << "m ";
260 break;
261
263 writeXY (i.x1, i.y1);
264 lastX = i.x1;
265 lastY = i.y1;
266 out << "l ";
267 break;
268
270 {
271 const float cp1x = lastX + (i.x1 - lastX) * 2.0f / 3.0f;
272 const float cp1y = lastY + (i.y1 - lastY) * 2.0f / 3.0f;
273 const float cp2x = cp1x + (i.x2 - lastX) / 3.0f;
274 const float cp2y = cp1y + (i.y2 - lastY) / 3.0f;
275
276 writeXY (cp1x, cp1y);
277 writeXY (cp2x, cp2y);
278 writeXY (i.x2, i.y2);
279 out << "ct ";
280 lastX = i.x2;
281 lastY = i.y2;
282 }
283 break;
284
286 writeXY (i.x1, i.y1);
287 writeXY (i.x2, i.y2);
288 writeXY (i.x3, i.y3);
289 out << "ct ";
290 lastX = i.x3;
291 lastY = i.y3;
292 break;
293
295 out << "cp ";
296 break;
297
298 default:
300 break;
301 }
302 }
303
304 out << '\n';
305}
306
307void LowLevelGraphicsPostScriptRenderer::writeTransform (const AffineTransform& trans) const
308{
309 out << "[ "
310 << trans.mat00 << ' '
311 << trans.mat10 << ' '
312 << trans.mat01 << ' '
313 << trans.mat11 << ' '
314 << trans.mat02 << ' '
315 << trans.mat12 << " ] concat ";
316}
317
318//==============================================================================
319void LowLevelGraphicsPostScriptRenderer::setFill (const FillType& fillType)
320{
321 stateStack.getLast()->fillType = fillType;
322}
323
324void LowLevelGraphicsPostScriptRenderer::setOpacity (float /*opacity*/)
325{
326}
327
328void LowLevelGraphicsPostScriptRenderer::setInterpolationQuality (Graphics::ResamplingQuality /*quality*/)
329{
330}
331
332//==============================================================================
333void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle<int>& r, const bool /*replaceExistingContents*/)
334{
335 fillRect (r.toFloat());
336}
337
338void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle<float>& r)
339{
340 if (stateStack.getLast()->fillType.isColour())
341 {
342 writeClip();
343 writeColour (stateStack.getLast()->fillType.colour);
344
345 auto r2 = r.translated ((float) stateStack.getLast()->xOffset,
346 (float) stateStack.getLast()->yOffset);
347
348 out << r2.getX() << ' ' << -r2.getBottom() << ' ' << r2.getWidth() << ' ' << r2.getHeight() << " rectfill\n";
349 }
350 else
351 {
352 Path p;
353 p.addRectangle (r);
354 fillPath (p, AffineTransform());
355 }
356}
357
358void LowLevelGraphicsPostScriptRenderer::fillRectList (const RectangleList<float>& list)
359{
360 fillPath (list.toPath(), AffineTransform());
361}
362
363//==============================================================================
364void LowLevelGraphicsPostScriptRenderer::fillPath (const Path& path, const AffineTransform& t)
365{
366 if (stateStack.getLast()->fillType.isColour())
367 {
368 writeClip();
369
370 Path p (path);
371 p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset,
372 (float) stateStack.getLast()->yOffset));
373 writePath (p);
374
375 writeColour (stateStack.getLast()->fillType.colour);
376
377 out << "fill\n";
378 }
379 else if (stateStack.getLast()->fillType.isGradient())
380 {
381 // this doesn't work correctly yet - it could be improved to handle solid gradients, but
382 // postscript can't do semi-transparent ones.
383 notPossibleInPostscriptAssert; // you can disable this warning by setting the WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS flag at the top of this file
384
385 writeClip();
386 out << "gsave ";
387
388 {
389 Path p (path);
390 p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset));
391 writePath (p);
392 out << "clip\n";
393 }
394
395 auto bounds = stateStack.getLast()->clip.getBounds();
396
397 // ideally this would draw lots of lines or ellipses to approximate the gradient, but for the
398 // time-being, this just fills it with the average colour..
399 writeColour (stateStack.getLast()->fillType.gradient->getColourAtPosition (0.5f));
400 out << bounds.getX() << ' ' << -bounds.getBottom() << ' ' << bounds.getWidth() << ' ' << bounds.getHeight() << " rectfill\n";
401
402 out << "grestore\n";
403 }
404}
405
406//==============================================================================
407void LowLevelGraphicsPostScriptRenderer::writeImage (const Image& im,
408 const int sx, const int sy,
409 const int maxW, const int maxH) const
410{
411 out << "{<\n";
412
413 const int w = jmin (maxW, im.getWidth());
414 const int h = jmin (maxH, im.getHeight());
415
416 int charsOnLine = 0;
417 const Image::BitmapData srcData (im, 0, 0, w, h);
418 Colour pixel;
419
420 for (int y = h; --y >= 0;)
421 {
422 for (int x = 0; x < w; ++x)
423 {
424 const uint8* pixelData = srcData.getPixelPointer (x, y);
425
426 if (x >= sx && y >= sy)
427 {
428 if (im.isARGB())
429 {
430 PixelARGB p (*(const PixelARGB*) pixelData);
431 p.unpremultiply();
432 pixel = Colours::white.overlaidWith (Colour (p));
433 }
434 else if (im.isRGB())
435 {
436 pixel = Colour (*((const PixelRGB*) pixelData));
437 }
438 else
439 {
440 pixel = Colour ((uint8) 0, (uint8) 0, (uint8) 0, *pixelData);
441 }
442 }
443 else
444 {
445 pixel = Colours::transparentWhite;
446 }
447
448 const uint8 pixelValues[3] = { pixel.getRed(), pixel.getGreen(), pixel.getBlue() };
449
450 out << String::toHexString (pixelValues, 3, 0);
451 charsOnLine += 3;
452
453 if (charsOnLine > 100)
454 {
455 out << '\n';
456 charsOnLine = 0;
457 }
458 }
459 }
460
461 out << "\n>}\n";
462}
463
464void LowLevelGraphicsPostScriptRenderer::drawImage (const Image& sourceImage, const AffineTransform& transform)
465{
466 const int w = sourceImage.getWidth();
467 const int h = sourceImage.getHeight();
468
469 writeClip();
470
471 out << "gsave ";
472 writeTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset)
473 .scaled (1.0f, -1.0f));
474
475 RectangleList<int> imageClip;
476 sourceImage.createSolidAreaMask (imageClip, 0.5f);
477
478 out << "newpath ";
479 int itemsOnLine = 0;
480
481 for (auto& i : imageClip)
482 {
483 if (++itemsOnLine == 6)
484 {
485 out << '\n';
486 itemsOnLine = 0;
487 }
488
489 out << i.getX() << ' ' << i.getY() << ' ' << i.getWidth() << ' ' << i.getHeight() << " pr ";
490 }
491
492 out << " clip newpath\n";
493
494 out << w << ' ' << h << " scale\n";
495 out << w << ' ' << h << " 8 [" << w << " 0 0 -" << h << ' ' << (int) 0 << ' ' << h << " ]\n";
496
497 writeImage (sourceImage, 0, 0, w, h);
498
499 out << "false 3 colorimage grestore\n";
500 needToClip = true;
501}
502
503
504//==============================================================================
505void LowLevelGraphicsPostScriptRenderer::drawLine (const Line <float>& line)
506{
507 Path p;
508 p.addLineSegment (line, 1.0f);
509 fillPath (p, AffineTransform());
510}
511
512//==============================================================================
513void LowLevelGraphicsPostScriptRenderer::setFont (const Font& newFont)
514{
515 stateStack.getLast()->font = newFont;
516}
517
518const Font& LowLevelGraphicsPostScriptRenderer::getFont()
519{
520 return stateStack.getLast()->font;
521}
522
523void LowLevelGraphicsPostScriptRenderer::drawGlyph (int glyphNumber, const AffineTransform& transform)
524{
525 Path p;
526 Font& font = stateStack.getLast()->font;
527 font.getTypefacePtr()->getOutlineForGlyph (glyphNumber, p);
528 fillPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()).followedBy (transform));
529}
530
531} // namespace juce
Represents a 2D affine-transformation matrix.
static AffineTransform scale(float factorX, float factorY) noexcept
Returns a new transform which is a re-scale about the origin.
AffineTransform followedBy(const AffineTransform &other) const noexcept
Returns the result of concatenating another transformation after this one.
ResamplingQuality
Types of rendering quality that can be specified when drawing images.
@ quadraticTo
For this type, x1, y1, x2, y2 indicate the control point and endpoint of a quadratic curve.
Definition juce_Path.h:745
@ closePath
Indicates that the sub-path is being closed.
Definition juce_Path.h:747
@ lineTo
For this type, x1 and y1 indicate the end point of the line.
Definition juce_Path.h:744
@ cubicTo
For this type, x1, y1, x2, y2, x3, y3 indicate the two control points and the endpoint of a cubic cur...
Definition juce_Path.h:746
@ startNewSubPath
For this type, x1 and y1 will be set to indicate the first point in the subpath.
Definition juce_Path.h:743
A pair of (x, y) coordinates.
Definition juce_Point.h:42
constexpr bool isOrigin() const noexcept
Returns true if the point is (0, 0).
Definition juce_Point.h:66
ValueType y
The point's Y coordinate.
Definition juce_Point.h:252
ValueType x
The point's X coordinate.
Definition juce_Point.h:251
static String toHexString(IntegerType number)
Returns a string representing this numeric value in hexadecimal.
#define jassert(expression)
Platform-independent assertion macro.
#define jassertfalse
This will always cause an assertion failure.
typedef int
JUCE Namespace.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
unsigned char uint8
A platform-independent 8-bit unsigned integer type.
T transform(T... args)