540 lines
16 KiB
C++
540 lines
16 KiB
C++
|
/*
|
||
|
==============================================================================
|
||
|
|
||
|
This file is part of the JUCE library.
|
||
|
Copyright (c) 2020 - Raw Material Software Limited
|
||
|
|
||
|
JUCE is an open source library subject to commercial or open-source
|
||
|
licensing.
|
||
|
|
||
|
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||
|
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||
|
|
||
|
End User License Agreement: www.juce.com/juce-6-licence
|
||
|
Privacy Policy: www.juce.com/juce-privacy-policy
|
||
|
|
||
|
Or: You may also use this code under the terms of the GPL v3 (see
|
||
|
www.gnu.org/licenses).
|
||
|
|
||
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||
|
DISCLAIMED.
|
||
|
|
||
|
==============================================================================
|
||
|
*/
|
||
|
|
||
|
namespace juce
|
||
|
{
|
||
|
|
||
|
// this will throw an assertion if you try to draw something that's not
|
||
|
// possible in postscript
|
||
|
#define WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS 0
|
||
|
|
||
|
//==============================================================================
|
||
|
#if JUCE_DEBUG && WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS
|
||
|
#define notPossibleInPostscriptAssert jassertfalse
|
||
|
#else
|
||
|
#define notPossibleInPostscriptAssert
|
||
|
#endif
|
||
|
|
||
|
//==============================================================================
|
||
|
LowLevelGraphicsPostScriptRenderer::LowLevelGraphicsPostScriptRenderer (OutputStream& resultingPostScript,
|
||
|
const String& documentTitle,
|
||
|
const int totalWidth_,
|
||
|
const int totalHeight_)
|
||
|
: out (resultingPostScript),
|
||
|
totalWidth (totalWidth_),
|
||
|
totalHeight (totalHeight_),
|
||
|
needToClip (true)
|
||
|
{
|
||
|
stateStack.add (new SavedState());
|
||
|
stateStack.getLast()->clip = Rectangle<int> (totalWidth_, totalHeight_);
|
||
|
|
||
|
const float scale = jmin ((520.0f / (float) totalWidth_), (750.0f / (float) totalHeight));
|
||
|
|
||
|
out << "%!PS-Adobe-3.0 EPSF-3.0"
|
||
|
"\n%%BoundingBox: 0 0 600 824"
|
||
|
"\n%%Pages: 0"
|
||
|
"\n%%Creator: Raw Material Software Limited - JUCE"
|
||
|
"\n%%Title: " << documentTitle <<
|
||
|
"\n%%CreationDate: none"
|
||
|
"\n%%LanguageLevel: 2"
|
||
|
"\n%%EndComments"
|
||
|
"\n%%BeginProlog"
|
||
|
"\n%%BeginResource: JRes"
|
||
|
"\n/bd {bind def} bind def"
|
||
|
"\n/c {setrgbcolor} bd"
|
||
|
"\n/m {moveto} bd"
|
||
|
"\n/l {lineto} bd"
|
||
|
"\n/rl {rlineto} bd"
|
||
|
"\n/ct {curveto} bd"
|
||
|
"\n/cp {closepath} bd"
|
||
|
"\n/pr {3 index 3 index moveto 1 index 0 rlineto 0 1 index rlineto pop neg 0 rlineto pop pop closepath} bd"
|
||
|
"\n/doclip {initclip newpath} bd"
|
||
|
"\n/endclip {clip newpath} bd"
|
||
|
"\n%%EndResource"
|
||
|
"\n%%EndProlog"
|
||
|
"\n%%BeginSetup"
|
||
|
"\n%%EndSetup"
|
||
|
"\n%%Page: 1 1"
|
||
|
"\n%%BeginPageSetup"
|
||
|
"\n%%EndPageSetup\n\n"
|
||
|
<< "40 800 translate\n"
|
||
|
<< scale << ' ' << scale << " scale\n\n";
|
||
|
}
|
||
|
|
||
|
LowLevelGraphicsPostScriptRenderer::~LowLevelGraphicsPostScriptRenderer()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
bool LowLevelGraphicsPostScriptRenderer::isVectorDevice() const
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::setOrigin (Point<int> o)
|
||
|
{
|
||
|
if (! o.isOrigin())
|
||
|
{
|
||
|
stateStack.getLast()->xOffset += o.x;
|
||
|
stateStack.getLast()->yOffset += o.y;
|
||
|
needToClip = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::addTransform (const AffineTransform& /*transform*/)
|
||
|
{
|
||
|
//xxx
|
||
|
jassertfalse;
|
||
|
}
|
||
|
|
||
|
float LowLevelGraphicsPostScriptRenderer::getPhysicalPixelScaleFactor() { return 1.0f; }
|
||
|
|
||
|
bool LowLevelGraphicsPostScriptRenderer::clipToRectangle (const Rectangle<int>& r)
|
||
|
{
|
||
|
needToClip = true;
|
||
|
return stateStack.getLast()->clip.clipTo (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset));
|
||
|
}
|
||
|
|
||
|
bool LowLevelGraphicsPostScriptRenderer::clipToRectangleList (const RectangleList<int>& clipRegion)
|
||
|
{
|
||
|
needToClip = true;
|
||
|
return stateStack.getLast()->clip.clipTo (clipRegion);
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::excludeClipRectangle (const Rectangle<int>& r)
|
||
|
{
|
||
|
needToClip = true;
|
||
|
stateStack.getLast()->clip.subtract (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset));
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::clipToPath (const Path& path, const AffineTransform& transform)
|
||
|
{
|
||
|
writeClip();
|
||
|
|
||
|
Path p (path);
|
||
|
p.applyTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset));
|
||
|
writePath (p);
|
||
|
out << "clip\n";
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::clipToImageAlpha (const Image& /*sourceImage*/, const AffineTransform& /*transform*/)
|
||
|
{
|
||
|
needToClip = true;
|
||
|
jassertfalse; // xxx
|
||
|
}
|
||
|
|
||
|
bool LowLevelGraphicsPostScriptRenderer::clipRegionIntersects (const Rectangle<int>& r)
|
||
|
{
|
||
|
return stateStack.getLast()->clip.intersectsRectangle (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset));
|
||
|
}
|
||
|
|
||
|
Rectangle<int> LowLevelGraphicsPostScriptRenderer::getClipBounds() const
|
||
|
{
|
||
|
return stateStack.getLast()->clip.getBounds().translated (-stateStack.getLast()->xOffset,
|
||
|
-stateStack.getLast()->yOffset);
|
||
|
}
|
||
|
|
||
|
bool LowLevelGraphicsPostScriptRenderer::isClipEmpty() const
|
||
|
{
|
||
|
return stateStack.getLast()->clip.isEmpty();
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
LowLevelGraphicsPostScriptRenderer::SavedState::SavedState()
|
||
|
: xOffset (0),
|
||
|
yOffset (0)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
LowLevelGraphicsPostScriptRenderer::SavedState::~SavedState()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::saveState()
|
||
|
{
|
||
|
stateStack.add (new SavedState (*stateStack.getLast()));
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::restoreState()
|
||
|
{
|
||
|
jassert (stateStack.size() > 0);
|
||
|
|
||
|
if (stateStack.size() > 0)
|
||
|
stateStack.removeLast();
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::beginTransparencyLayer (float)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::endTransparencyLayer()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void LowLevelGraphicsPostScriptRenderer::writeClip()
|
||
|
{
|
||
|
if (needToClip)
|
||
|
{
|
||
|
needToClip = false;
|
||
|
|
||
|
out << "doclip ";
|
||
|
|
||
|
int itemsOnLine = 0;
|
||
|
|
||
|
for (auto& i : stateStack.getLast()->clip)
|
||
|
{
|
||
|
if (++itemsOnLine == 6)
|
||
|
{
|
||
|
itemsOnLine = 0;
|
||
|
out << '\n';
|
||
|
}
|
||
|
|
||
|
out << i.getX() << ' ' << -i.getY() << ' '
|
||
|
<< i.getWidth() << ' ' << -i.getHeight() << " pr ";
|
||
|
}
|
||
|
|
||
|
out << "endclip\n";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::writeColour (Colour colour)
|
||
|
{
|
||
|
Colour c (Colours::white.overlaidWith (colour));
|
||
|
|
||
|
if (lastColour != c)
|
||
|
{
|
||
|
lastColour = c;
|
||
|
|
||
|
out << String (c.getFloatRed(), 3) << ' '
|
||
|
<< String (c.getFloatGreen(), 3) << ' '
|
||
|
<< String (c.getFloatBlue(), 3) << " c\n";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::writeXY (const float x, const float y) const
|
||
|
{
|
||
|
out << String (x, 2) << ' '
|
||
|
<< String (-y, 2) << ' ';
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::writePath (const Path& path) const
|
||
|
{
|
||
|
out << "newpath ";
|
||
|
|
||
|
float lastX = 0.0f;
|
||
|
float lastY = 0.0f;
|
||
|
int itemsOnLine = 0;
|
||
|
|
||
|
Path::Iterator i (path);
|
||
|
|
||
|
while (i.next())
|
||
|
{
|
||
|
if (++itemsOnLine == 4)
|
||
|
{
|
||
|
itemsOnLine = 0;
|
||
|
out << '\n';
|
||
|
}
|
||
|
|
||
|
switch (i.elementType)
|
||
|
{
|
||
|
case Path::Iterator::startNewSubPath:
|
||
|
writeXY (i.x1, i.y1);
|
||
|
lastX = i.x1;
|
||
|
lastY = i.y1;
|
||
|
out << "m ";
|
||
|
break;
|
||
|
|
||
|
case Path::Iterator::lineTo:
|
||
|
writeXY (i.x1, i.y1);
|
||
|
lastX = i.x1;
|
||
|
lastY = i.y1;
|
||
|
out << "l ";
|
||
|
break;
|
||
|
|
||
|
case Path::Iterator::quadraticTo:
|
||
|
{
|
||
|
const float cp1x = lastX + (i.x1 - lastX) * 2.0f / 3.0f;
|
||
|
const float cp1y = lastY + (i.y1 - lastY) * 2.0f / 3.0f;
|
||
|
const float cp2x = cp1x + (i.x2 - lastX) / 3.0f;
|
||
|
const float cp2y = cp1y + (i.y2 - lastY) / 3.0f;
|
||
|
|
||
|
writeXY (cp1x, cp1y);
|
||
|
writeXY (cp2x, cp2y);
|
||
|
writeXY (i.x2, i.y2);
|
||
|
out << "ct ";
|
||
|
lastX = i.x2;
|
||
|
lastY = i.y2;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case Path::Iterator::cubicTo:
|
||
|
writeXY (i.x1, i.y1);
|
||
|
writeXY (i.x2, i.y2);
|
||
|
writeXY (i.x3, i.y3);
|
||
|
out << "ct ";
|
||
|
lastX = i.x3;
|
||
|
lastY = i.y3;
|
||
|
break;
|
||
|
|
||
|
case Path::Iterator::closePath:
|
||
|
out << "cp ";
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
jassertfalse;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
out << '\n';
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::writeTransform (const AffineTransform& trans) const
|
||
|
{
|
||
|
out << "[ "
|
||
|
<< trans.mat00 << ' '
|
||
|
<< trans.mat10 << ' '
|
||
|
<< trans.mat01 << ' '
|
||
|
<< trans.mat11 << ' '
|
||
|
<< trans.mat02 << ' '
|
||
|
<< trans.mat12 << " ] concat ";
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void LowLevelGraphicsPostScriptRenderer::setFill (const FillType& fillType)
|
||
|
{
|
||
|
stateStack.getLast()->fillType = fillType;
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::setOpacity (float /*opacity*/)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::setInterpolationQuality (Graphics::ResamplingQuality /*quality*/)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle<int>& r, const bool /*replaceExistingContents*/)
|
||
|
{
|
||
|
fillRect (r.toFloat());
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle<float>& r)
|
||
|
{
|
||
|
if (stateStack.getLast()->fillType.isColour())
|
||
|
{
|
||
|
writeClip();
|
||
|
writeColour (stateStack.getLast()->fillType.colour);
|
||
|
|
||
|
auto r2 = r.translated ((float) stateStack.getLast()->xOffset,
|
||
|
(float) stateStack.getLast()->yOffset);
|
||
|
|
||
|
out << r2.getX() << ' ' << -r2.getBottom() << ' ' << r2.getWidth() << ' ' << r2.getHeight() << " rectfill\n";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Path p;
|
||
|
p.addRectangle (r);
|
||
|
fillPath (p, AffineTransform());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::fillRectList (const RectangleList<float>& list)
|
||
|
{
|
||
|
fillPath (list.toPath(), AffineTransform());
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void LowLevelGraphicsPostScriptRenderer::fillPath (const Path& path, const AffineTransform& t)
|
||
|
{
|
||
|
if (stateStack.getLast()->fillType.isColour())
|
||
|
{
|
||
|
writeClip();
|
||
|
|
||
|
Path p (path);
|
||
|
p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset,
|
||
|
(float) stateStack.getLast()->yOffset));
|
||
|
writePath (p);
|
||
|
|
||
|
writeColour (stateStack.getLast()->fillType.colour);
|
||
|
|
||
|
out << "fill\n";
|
||
|
}
|
||
|
else if (stateStack.getLast()->fillType.isGradient())
|
||
|
{
|
||
|
// this doesn't work correctly yet - it could be improved to handle solid gradients, but
|
||
|
// postscript can't do semi-transparent ones.
|
||
|
notPossibleInPostscriptAssert; // you can disable this warning by setting the WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS flag at the top of this file
|
||
|
|
||
|
writeClip();
|
||
|
out << "gsave ";
|
||
|
|
||
|
{
|
||
|
Path p (path);
|
||
|
p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset));
|
||
|
writePath (p);
|
||
|
out << "clip\n";
|
||
|
}
|
||
|
|
||
|
auto bounds = stateStack.getLast()->clip.getBounds();
|
||
|
|
||
|
// ideally this would draw lots of lines or ellipses to approximate the gradient, but for the
|
||
|
// time-being, this just fills it with the average colour..
|
||
|
writeColour (stateStack.getLast()->fillType.gradient->getColourAtPosition (0.5f));
|
||
|
out << bounds.getX() << ' ' << -bounds.getBottom() << ' ' << bounds.getWidth() << ' ' << bounds.getHeight() << " rectfill\n";
|
||
|
|
||
|
out << "grestore\n";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void LowLevelGraphicsPostScriptRenderer::writeImage (const Image& im,
|
||
|
const int sx, const int sy,
|
||
|
const int maxW, const int maxH) const
|
||
|
{
|
||
|
out << "{<\n";
|
||
|
|
||
|
const int w = jmin (maxW, im.getWidth());
|
||
|
const int h = jmin (maxH, im.getHeight());
|
||
|
|
||
|
int charsOnLine = 0;
|
||
|
const Image::BitmapData srcData (im, 0, 0, w, h);
|
||
|
Colour pixel;
|
||
|
|
||
|
for (int y = h; --y >= 0;)
|
||
|
{
|
||
|
for (int x = 0; x < w; ++x)
|
||
|
{
|
||
|
const uint8* pixelData = srcData.getPixelPointer (x, y);
|
||
|
|
||
|
if (x >= sx && y >= sy)
|
||
|
{
|
||
|
if (im.isARGB())
|
||
|
{
|
||
|
PixelARGB p (*(const PixelARGB*) pixelData);
|
||
|
p.unpremultiply();
|
||
|
pixel = Colours::white.overlaidWith (Colour (p));
|
||
|
}
|
||
|
else if (im.isRGB())
|
||
|
{
|
||
|
pixel = Colour (*((const PixelRGB*) pixelData));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pixel = Colour ((uint8) 0, (uint8) 0, (uint8) 0, *pixelData);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pixel = Colours::transparentWhite;
|
||
|
}
|
||
|
|
||
|
const uint8 pixelValues[3] = { pixel.getRed(), pixel.getGreen(), pixel.getBlue() };
|
||
|
|
||
|
out << String::toHexString (pixelValues, 3, 0);
|
||
|
charsOnLine += 3;
|
||
|
|
||
|
if (charsOnLine > 100)
|
||
|
{
|
||
|
out << '\n';
|
||
|
charsOnLine = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
out << "\n>}\n";
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::drawImage (const Image& sourceImage, const AffineTransform& transform)
|
||
|
{
|
||
|
const int w = sourceImage.getWidth();
|
||
|
const int h = sourceImage.getHeight();
|
||
|
|
||
|
writeClip();
|
||
|
|
||
|
out << "gsave ";
|
||
|
writeTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset)
|
||
|
.scaled (1.0f, -1.0f));
|
||
|
|
||
|
RectangleList<int> imageClip;
|
||
|
sourceImage.createSolidAreaMask (imageClip, 0.5f);
|
||
|
|
||
|
out << "newpath ";
|
||
|
int itemsOnLine = 0;
|
||
|
|
||
|
for (auto& i : imageClip)
|
||
|
{
|
||
|
if (++itemsOnLine == 6)
|
||
|
{
|
||
|
out << '\n';
|
||
|
itemsOnLine = 0;
|
||
|
}
|
||
|
|
||
|
out << i.getX() << ' ' << i.getY() << ' ' << i.getWidth() << ' ' << i.getHeight() << " pr ";
|
||
|
}
|
||
|
|
||
|
out << " clip newpath\n";
|
||
|
|
||
|
out << w << ' ' << h << " scale\n";
|
||
|
out << w << ' ' << h << " 8 [" << w << " 0 0 -" << h << ' ' << (int) 0 << ' ' << h << " ]\n";
|
||
|
|
||
|
writeImage (sourceImage, 0, 0, w, h);
|
||
|
|
||
|
out << "false 3 colorimage grestore\n";
|
||
|
needToClip = true;
|
||
|
}
|
||
|
|
||
|
|
||
|
//==============================================================================
|
||
|
void LowLevelGraphicsPostScriptRenderer::drawLine (const Line <float>& line)
|
||
|
{
|
||
|
Path p;
|
||
|
p.addLineSegment (line, 1.0f);
|
||
|
fillPath (p, AffineTransform());
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void LowLevelGraphicsPostScriptRenderer::setFont (const Font& newFont)
|
||
|
{
|
||
|
stateStack.getLast()->font = newFont;
|
||
|
}
|
||
|
|
||
|
const Font& LowLevelGraphicsPostScriptRenderer::getFont()
|
||
|
{
|
||
|
return stateStack.getLast()->font;
|
||
|
}
|
||
|
|
||
|
void LowLevelGraphicsPostScriptRenderer::drawGlyph (int glyphNumber, const AffineTransform& transform)
|
||
|
{
|
||
|
Path p;
|
||
|
Font& font = stateStack.getLast()->font;
|
||
|
font.getTypefacePtr()->getOutlineForGlyph (glyphNumber, p);
|
||
|
fillPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()).followedBy (transform));
|
||
|
}
|
||
|
|
||
|
} // namespace juce
|