/* ============================================================================== This file is part of the JUCE 6 technical preview. Copyright (c) 2020 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For this technical preview, this file is not subject to commercial licensing. 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 (totalWidth_, totalHeight_); const float scale = jmin ((520.0f / totalWidth_), (750.0f / 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 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& r) { needToClip = true; return stateStack.getLast()->clip.clipTo (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); } bool LowLevelGraphicsPostScriptRenderer::clipToRectangleList (const RectangleList& clipRegion) { needToClip = true; return stateStack.getLast()->clip.clipTo (clipRegion); } void LowLevelGraphicsPostScriptRenderer::excludeClipRectangle (const Rectangle& 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& r) { return stateStack.getLast()->clip.intersectsRectangle (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); } Rectangle 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& r, const bool /*replaceExistingContents*/) { fillRect (r.toFloat()); } void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle& 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& 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 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 & 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.getTypeface()->getOutlineForGlyph (glyphNumber, p); fillPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()).followedBy (transform)); } } // namespace juce