How-to work with 3rd Party LowLevelGraphicsContext (Cairo, AGG ...)


#1

Hi,

Sometimes by using JUCE in a document editing context, you'll be restricted by a lack of features, it's a pitty and it's the hole that make JUCE "oriented audio" from other GUI toolkits. About the "oriented audio", it's not false. JUCE was basically a "consequence" of the famous Tracktion audio software. But water has flowed under the bridge since that time.

The first one and, in my own opinion, the major lack is about text editing. A lot of discussion in this forum talk about the limitations of the TextEditor class (justification, color, font ...). This is the more frustrating class of the Toolkit. I take a look sometimes to do my own class, but Jules is right, making a correct implementation of a Rich Text Editor is not a trivial problem.

Others lack of classes was about output of document. No PDF class, no more of Printing class. In other words, no printing capabilities even by considering that export PDF can be printed directly in Acrobat or another PDF reader. In my knowledge there's no correct crossplatform Printing library available at this day (commercial or not).

So, some years ago i've work on experimental use of Cairo and AGG. AGG is hard to use, not because it's fully template oriented (very flexible, but shitty to write), but because you need to understand the philosophy of the classes and make a serious study of the good documentation available. AGG it's not dedicated to printing but it's a very high quality rasterizer engine. By using a good pipeline of AGG template you can have a similar quality as Photoshop (better for very small size bitmap or vectorial process).

For example, a simple Bicubic interpolator for resizing JUCE Image by using a AGG pipeline. Don't hesitate to test with other filters, take a look to the AGG demo of Image Filter : http://www.antigrain.com/demo/image_filters.cpp.html

//============================================================================

#include "agg_pixfmt_gray.h"
#include "agg_pixfmt_rgb.h"
#include "agg_pixfmt_rgb_packed.h"
#include "agg_pixfmt_rgba.h"
//#include "agg_pixfmt_rgb24_lcd.h"
#include "agg_pixfmt_transposer.h"
//============================================================================
#include "agg_rendering_buffer.h"
//============================================================================
#include "agg_renderer_scanline.h"
//============================================================================
#include "agg_rasterizer_scanline_aa.h"
#include "agg_rasterizer_outline.h"
//============================================================================
#include "agg_scanline_u.h"
#include "agg_scanline_p.h"
//============================================================================
#include "agg_span_allocator.h"
#include "agg_span_solid.h"
#include "agg_span_pattern_gray.h"
#include "agg_span_pattern_rgb.h"
#include "agg_span_pattern_rgba.h"
#include "agg_span_gouraud_rgba.h"
#include "agg_span_gouraud_gray.h"
#include "agg_span_interpolator_linear.h"
#include "agg_span_interpolator_trans.h"
#include "agg_span_subdiv_adaptor.h"
#include "agg_span_image_filter_rgb.h"
#include "agg_span_image_filter_rgba.h"
//============================================================================
#include "agg_image_accessors.h"
//============================================================================
#include "agg_path_storage.h"
//============================================================================
#include "agg_trans_affine.h"
#include "agg_trans_bilinear.h"
//============================================================================
#include "agg_conv_curve.h"
#include "agg_conv_contour.h"
#include "agg_conv_transform.h"
//============================================================================
#include "agg_trans_perspective.h"
//============================================================================
//#include "agg_font_freetype.h"
//============================================================================
#include "agg_gamma_lut.h"
//============================================================================
#define span_image_filter_2x2 agg::span_image_filter_rgba_2x2
#define span_image_filter_nn  agg::span_image_filter_rgba_nn
//==============================================================================
#include "agg_ellipse.h"
#include "agg_rounded_rect.h"
//==============================================================================
#include "agg_image_filters.h"
#include "agg_span_image_filter_rgb.h"
#include "agg_span_image_filter_rgba.h"
#include "agg_span_image_filter_gray.h"
//==============================================================================
#include "agg_span_allocator.h"
#include "agg_span_interpolator_linear.h"
//==============================================================================
#include "agg_image_accessors.h"
//==============================================================================
#include "agg_span_image_filter_rgb.h"
//==============================================================================
#define span_image_filter          span_image_filter_rgb
#define span_image_filter_nn       span_image_filter_rgb_nn
#define span_image_filter_bilinear span_image_filter_rgb_bilinear_clip
#define span_image_filter_2x2      span_image_filter_rgb_2x2
//==============================================================================
static void rescaleImage (Image& src, Image& dst)
{
    /** Wrapping to write on Juce Image.
    */
    double sw, sh, dw, dh;
    int lineStride, pixelStride;
    agg::rendering_buffer sbuffer;
    agg::rendering_buffer dbuffer;
    sw = src.getWidth(); sh = src.getHeight();
    Image::BitmapData bdsrc (src, 0, 0, sw, sh, Image::BitmapData::readOnly);
    sbuffer.attach ((unsigned char*)bdsrc.data, sw, sh, bdsrc.lineStride);
    dw = dst.getWidth(); dh = dst.getHeight();
    Image::BitmapData bddst (dst, 0, 0, dw, dh, Image::BitmapData::writeOnly);
    dbuffer.attach ((unsigned char*)bddst.data, dw, dh, bddst.lineStride);

    /** AGG work
    */
    typedef agg::pixfmt_bgra32 pixel_format;
    typedef agg::pixfmt_bgra32_pre pixel_format_pre;
    typedef pixel_format::color_type color_type;
    typedef agg::renderer_base <pixel_format> renderer_base;
    typedef agg::renderer_base <pixel_format_pre> renderer_base_pre;

    pixel_format pixf (dbuffer);
    pixel_format_pre pixf_pre (dbuffer);
    renderer_base rb (pixf);
    renderer_base_pre rb_pre (pixf_pre);
    agg::rasterizer_scanline_aa<> ras;
    agg::scanline_u8 sl;
    agg::span_allocator<color_type> sa;

    agg::trans_affine src_mtx;
    agg::trans_affine img_mtx = src_mtx;
    img_mtx *= agg::trans_affine_scaling (sw/dw);
    agg::rounded_rect rect(0, 0, dw, dh, 1);
    agg::conv_transform <agg::rounded_rect> tr (rect, src_mtx);
    typedef agg::span_interpolator_linear<> interpolator_type;
    interpolator_type interpolator (img_mtx);
    typedef agg::image_accessor_clip <pixel_format> source_type;
    pixel_format pixf_img (sbuffer);
    source_type source (pixf_img, agg::rgba_pre(0,0,0,0));
    agg::image_filter_lut filter;
    filter.calculate (agg::image_filter_bicubic(), false);
    typedef agg::span_image_filter <source_type, interpolator_type> span_gen_type;
    span_gen_type sg (source, interpolator, filter);
    ras.add_path (tr);
    agg::render_scanlines_aa (ras, sl, rb_pre, sa, sg);
}
//==============================================================================

This is a simple example of using AGG with JUCE material. Do not considerate this sample more than an example.

It's sometimes better to use a header to compile you're includes and typedef (shortcut for template style), like that :

#ifndef __AGGWRAPPER_INCLUDE_H__
#define __AGGWRAPPER_INCLUDE_H__
#include "agg_basics.h"
/*
    RENDERING BUFFER
*/
#include "agg_rendering_buffer.h"
/*
    PIXEL FORMAT
*/
#include "agg_pixfmt_amask_adaptor.h"
#include "agg_pixfmt_gray.h"
#include "agg_pixfmt_rgb.h"
#include "agg_pixfmt_rgb_packed.h"
#include "agg_pixfmt_rgba.h"
#include "agg_pixfmt_transposer.h"
/*
    RENDERER
*/
#include "agg_renderer_base.h"    
#include "agg_renderer_mclip.h"
#include "agg_renderer_outline_aa.h"
#include "agg_renderer_scanline.h"
/*  
    RASTERIZER
*/
#include "agg_rasterizer_outline.h"
#include "agg_rasterizer_outline_aa.h"
#include "agg_rasterizer_scanline_aa.h"    
#include "agg_scanline_bin.h"
#include "agg_scanline_u.h"
#include "agg_scanline_p.h"
/*
    CONVERTER
*/
#include "agg_conv_contour.h"
#include "agg_conv_curve.h"
#include "agg_conv_stroke.h"
#include "agg_conv_transform.h"    
#include "agg_conv_gpc.h"
#include "agg_conv_clip_polygon.h"
/*
    TRANSFORMER
*/
#include "agg_trans_affine.h"
/*
    PATH STORAGE
*/
#include "agg_path_storage.h"
/*
    AGG WRAPPER
*/
using namespace agg;

/* Pixel Format */
//typedef pixfmt_alpha_blend_rgba<blender_rgba<rgba8, order_argb>, rendering_buffer>    pixel_format;
typedef pixfmt_bgra32 pixel_format;
typedef pixel_format::color_type color_type;
//typedef path_base<vertex_block_storage<float> > path_storage;

/* Basic Renderer */
typedef renderer_base <pixel_format>                                render_base;

/* Auxiliar Renderer */
typedef renderer_outline_aa <render_base>                            render_outline_aa;
typedef renderer_scanline_aa_solid <render_base>                    render_scanline_aa_solid;
typedef renderer_scanline_bin_solid <render_base>                    render_scanline_bin_solid;

/* Rasterizer */
//typedef rasterizer_outline <>                                        raster_outline;
//typedef rasterizer_outline_aa <render_outline_aa>                    raster_outline_aa;
typedef rasterizer_scanline_aa <>                                    raster_scanline_aa;

#endif

AGG code can be write inside a LowLevelGraphicsContext :

LowLevelGraphicsAGGRenderer.h

//=============================================================================
#ifndef __LOWLEVELGRAPHICSAGGCONTEXT_H__
#define __LOWLEVELGRAPHICSAGGCONTEXT_H__
//=============================================================================
#include "JuceHeader.h"
//=============================================================================
class AGGSavedState;
//=============================================================================
class LowLevelGraphicsAGGContext : public LowLevelGraphicsContext                                  
{
    public:
        LowLevelGraphicsAGGContext (Image* imageToRenderOn);
        ~LowLevelGraphicsAGGContext ();
        //=====================================================================
        float getPhysicalPixelScaleFactor();
        void setOrigin (juce::Point<int>);
        float getScaleFactor ();
        void endTransparencyLayer ();
        void addTransform (const AffineTransform &);
        void clipToImageAlpha (const Image &, const AffineTransform &);
        void beginTransparencyLayer (float);
        void drawImage (const Image &, const AffineTransform &, bool);
        //=====================================================================
        bool isVectorDevice () const;
        //=====================================================================
        void setOrigin (int x, int y);
        bool clipToRectangle (const juce::Rectangle<int>& r);
        bool clipToRectangleList (const RectangleList<int>& clipRegion);
        void excludeClipRectangle (const juce::Rectangle<int>& r);
        void clipToPath (const Path& path, 
                         const AffineTransform& transform);
        void clipToImageAlpha (const Image& sourceImage, 
                               const juce::Rectangle<int>& srcClip, 
                               const AffineTransform& transform);
        bool clipRegionIntersects (const juce::Rectangle<int>& r);
        juce::Rectangle<int> getClipBounds() const;
        bool isClipEmpty() const;
        void saveState();
        void restoreState();
        //=====================================================================
        void setFill (const FillType& fillType);
        void setOpacity (float newOpacity);
        void setInterpolationQuality (Graphics::ResamplingQuality quality);
        //=====================================================================
        void fillRect (const juce::Rectangle<float>& r);
        void fillRectList (const RectangleList<float>& rl);
        void fillRect (const juce::Rectangle<int>& r, bool replaceExistingContents);
        void fillPath (const Path& path, const AffineTransform& transform);
        //=====================================================================
        void drawImage (const Image&, const AffineTransform&);
        void drawImage (const Image& sourceImage, const juce::Rectangle<int>& srcClip,
                        const AffineTransform& transform, bool fillEntireClipAsTiles);
        //=====================================================================
        void drawLine (const Line <float>& line);
        void drawLine (double x1, double y1, double x2, double y2);
        void drawVerticalLine (int x, float top, float bottom);
        void drawVerticalLine (int x, double top, double bottom);
        void drawHorizontalLine (int y, float left, float right);
        void drawHorizontalLine (int y, double left, double right);
        //=====================================================================
        void setFont (const juce::Font& newFont);
        const juce::Font& getFont();
        void drawGlyph (int glyphNumber, const AffineTransform& transform);
        bool drawTextLayout (const AttributedString&, const juce::Rectangle<float>&);
        //=====================================================================
        juce_UseDebuggingNewOperator
    protected:
        Image* image;
        //=====================================================================
        ScopedPointer <AGGSavedState> currentState;
        OwnedArray <AGGSavedState> stateStack;
        //=====================================================================
};
//=============================================================================
#endif // __LOWLEVELGRAPHICSAGGCONTEXT_H__
//=============================================================================

LowLevelGraphicsAGGRenderer.cpp

//=============================================================================
#include "LowLevelGraphicsAGGRenderer.h"
//============================================================================
#include "agg_basics.h"
#include "agg_math.h"
//============================================================================
#include "agg_pixfmt_gray.h"
#include "agg_pixfmt_rgb.h"
#include "agg_pixfmt_rgb_packed.h"
#include "agg_pixfmt_rgba.h"
//#include "agg_pixfmt_rgb24_lcd.h"
#include "agg_pixfmt_transposer.h"
//============================================================================
#include "agg_rendering_buffer.h"
//============================================================================
#include "agg_renderer_scanline.h"
//============================================================================
#include "agg_rasterizer_scanline_aa.h"
#include "agg_rasterizer_outline.h"
//============================================================================
#include "agg_scanline_u.h"
#include "agg_scanline_p.h"
//============================================================================
#include "agg_span_allocator.h"
#include "agg_span_solid.h"
#include "agg_span_pattern_gray.h"
#include "agg_span_pattern_rgb.h"
#include "agg_span_pattern_rgba.h"
#include "agg_span_gouraud_rgba.h"
#include "agg_span_gouraud_gray.h"
#include "agg_span_interpolator_linear.h"
#include "agg_span_interpolator_trans.h"
#include "agg_span_subdiv_adaptor.h"
#include "agg_span_image_filter_rgb.h"
#include "agg_span_image_filter_rgba.h"
//============================================================================
#include "agg_image_accessors.h"
//============================================================================
#include "agg_path_storage.h"
//============================================================================
#include "agg_trans_affine.h"
#include "agg_trans_bilinear.h"
//============================================================================
#include "agg_conv_curve.h"
#include "agg_conv_contour.h"
#include "agg_conv_transform.h"
//============================================================================
#include "agg_trans_perspective.h"
//============================================================================
//#include "agg_font_freetype.h"
//============================================================================
#include "agg_gamma_lut.h"
//============================================================================
#define span_image_filter_2x2 agg::span_image_filter_rgba_2x2
#define span_image_filter_nn  agg::span_image_filter_rgba_nn
//============================================================================
//#define pix_format agg::pix_format_bgr24
//=============================================================================
namespace agg {
//=============================================================================
typedef pixfmt_alpha_blend_rgba<blender_rgba<rgba8,order_rgba>,rendering_buffer> pix_fmt;
typedef pixfmt_alpha_blend_rgba<blender_rgba<rgba8,order_rgba>,rendering_buffer> pix_fmt_path;
typedef pixfmt_alpha_blend_rgb <blender_rgb <rgba8,order_rgb>, rendering_buffer> pix_fmt_img;
//========================================================================
typedef agg::pixfmt_alpha_blend_rgba <agg::blender_rgba32, 
                                      agg::rendering_buffer, 
                                      agg::pixel32_type> pixfmt_type;
//========================================================================
typedef pix_fmt                                        pixelformat;
typedef pix_fmt                                        pixelformat_img;
typedef pix_fmt_path                                pixelformat_path;
//typedef pixfmt_type                                pixelformat_path;
//========================================================================
typedef pixelformat::color_type                        color_type;
typedef span_allocator <color_type>                    span_alloc_type;
//========================================================================
typedef renderer_base <pixelformat>                    renderer;
typedef renderer_base <pixelformat_path>            renderer_path;
typedef renderer_scanline_aa_solid <renderer_path>    solid_path;
//========================================================================
typedef wrap_mode_repeat_auto_pow2                    wrap_x_type;
typedef wrap_mode_repeat_auto_pow2                    wrap_y_type;
typedef image_accessor_wrap <pixelformat_img,
                             wrap_x_type,
                             wrap_y_type>            img_source_type;
//=============================================================================
} // namespace agg
//=============================================================================
template <class VertexSource> 
class FauxWeight
{
    public:
        FauxWeight (VertexSource& vs) 
          :    mtx_zoom_in_y (agg::trans_affine_scaling(1, 100)),
            mtx_zoom_out_y (agg::trans_affine_scaling(1, 1.0/100.0)),
            source (&vs),
            trans_zoom_in_y (*source, mtx_zoom_in_y),
            contour (trans_zoom_in_y),
            trans_zoom_out (contour, mtx_zoom_out_y) 
        {
            contour.auto_detect_orientation (false);
        }
        void weight (double v) { contour.width (v); }
        void rewind (unsigned path_id=0) { trans_zoom_out.rewind (path_id); }
        unsigned vertex (double* x, double* y) { return trans_zoom_out.vertex (x, y); }
    private:
        agg::trans_affine mtx_zoom_in_y;
        agg::trans_affine mtx_zoom_out_y;
        VertexSource* source;
        agg::conv_transform<VertexSource> trans_zoom_in_y;
        agg::conv_contour<agg::conv_transform<VertexSource>> contour;
        agg::conv_transform<agg::conv_contour<agg::conv_transform<VertexSource>>> trans_zoom_out;
};
//==============================================================================
static void dumpPath ( agg::rendering_buffer &buffer,
                       agg::path_storage path,
                       agg::trans_affine matrix,
                       Colour color )
{
    agg::pixelformat_path p (buffer);
    agg::renderer_path r (p);
    agg::solid_path s (r);
    double faux_weight = 0.0;
    agg::conv_transform <agg::path_storage> fill (path, matrix);
    agg::conv_curve <agg::conv_transform <agg::path_storage>> curve (fill);
    FauxWeight <agg::conv_curve <agg::conv_transform <agg::path_storage>>> weight (curve);
    
    /*
    agg::lcd_distribution_lut lut (1./3., 2./9., 1./9.);
    agg::pixfmt_rgb24_lcd pf_lcd (buffer, lut);
    agg::renderer_base <agg::pixfmt_rgb24_lcd> ren_base_lcd (pf_lcd);
    agg::renderer_scanline_aa_solid <agg::renderer_base <agg::pixfmt_rgb24_lcd>> ren_solid_lcd (ren_base_lcd);
    */
    agg::rasterizer_scanline_aa<> ras;
    agg::scanline_p8 sl;
    if (fabs(faux_weight) < 0.05)
        ras.add_path (curve);
    else
    {
        weight.weight (faux_weight);
        ras.add_path (weight);
    }
    s.color (agg::rgba8(color.getBlue(),
                        color.getGreen(),
                        color.getRed(),
                        color.getAlpha()));
    //ren_solid_lcd.color (rgba8(color.getRed(), color.getGreen(), color.getBlue()));
    agg::render_scanlines (ras, sl, s);
}
//=============================================================================
static void dumpImage ( agg::rendering_buffer &buffer,
                        agg::rendering_buffer &img_buffer,
                        agg::trans_affine matrix )
{
    agg::trans_perspective tr (matrix);
    if (tr.is_valid())
    {
        agg::pixelformat p (buffer);
        agg::renderer rb (p);
        agg::rasterizer_scanline_aa <> rasterizer;
        agg::scanline_u8 scanline;
        const uint32 w = buffer.width();
        const uint32 h = buffer.height();
        rasterizer.clip_box (0, 0, w, h);
        rasterizer.reset();
        rasterizer.move_to_d(0, 0);
        rasterizer.line_to_d(w, 0);
        rasterizer.line_to_d(w, h);
        rasterizer.line_to_d(0, h);
        agg::pixelformat_img format (img_buffer); 
        agg::img_source_type img_src (format);
        typedef agg::span_interpolator_linear_subdiv <agg::trans_perspective, 8> interpolator_type;
        typedef span_image_filter_2x2 <agg::img_source_type, interpolator_type> span_gen_type;
        interpolator_type interpolator (tr);
        agg::image_filter <agg::image_filter_hanning> filter;
        span_gen_type sg (img_src, interpolator, filter);
        agg::span_alloc_type sa;
        agg::render_scanlines_aa (rasterizer, scanline, rb, sa, sg);
    }
}
//==============================================================================
static agg::path_storage writePath (const Path& p)
{
    agg::path_storage path;
    Path::Iterator i (p);
    while (i.next())
    {
        switch (i.elementType)
        {
        case Path::Iterator::startNewSubPath:
            path.move_to(i.x1, i.y1);
            break;
        case Path::Iterator::lineTo:
            path.line_to(i.x1, i.y1);
            break;
        case Path::Iterator::quadraticTo:
            path.curve3 (i.x1, i.y1, i.x2, i.y2);
            break;
        case Path::Iterator::cubicTo:
            path.curve4 (i.x1, i.y1, i.x2, i.y2, i.x3, i.y3);
            break;
        case Path::Iterator::closePath:
            path.close_polygon();
            break;
        default:
            jassertfalse
            break;
        }
    }
    return path;
}
//==============================================================================
static agg::trans_affine writeAffineTransform (const AffineTransform& t)
{
    double sx = t.mat00; // scale X
    double sy = t.mat11; // scale Y
    double kx = t.mat01; // shear X
    double ky = t.mat10; // shear Y
    double tx = t.mat02; // trans X
    double ty = t.mat12; // trans Y
    agg::trans_affine mtx (sx, ky, kx, sy, tx, ty);
    //mtx.invert ();
    return mtx;
}
//============================================================================
class AGGSavedState
{
    public:
        AGGSavedState (const juce::Rectangle<int>& clip_, const int xOffset_, const int yOffset_,
                       const Font& font_, const FillType& fillType_,
                       const Graphics::ResamplingQuality interpolationQuality_) throw()
            : edgeTable (new EdgeTableHolder (EdgeTable (clip_))),
              xOffset (xOffset_), yOffset (yOffset_), font (font_), 
              fillType (fillType_), interpolationQuality (interpolationQuality_)
        {
        }
        //====================================================================
        AGGSavedState (const AGGSavedState& other) throw()
            : edgeTable (other.edgeTable), xOffset (other.xOffset),
              yOffset (other.yOffset), font (other.font),
              fillType (other.fillType), interpolationQuality (other.interpolationQuality)
        {
        }
        //====================================================================
        ~AGGSavedState () throw()
        {
        }
        //====================================================================
        bool clipToRectangle (const juce::Rectangle<int>& r) throw()
        {
            dupeEdgeTableIfMultiplyReferenced();
            edgeTable->edgeTable.clipToRectangle (r.translated (xOffset, yOffset));
            return ! edgeTable->edgeTable.isEmpty();
        }
        //====================================================================
        bool clipToRectangleList (const RectangleList<int>& r) throw()
        {
            dupeEdgeTableIfMultiplyReferenced();
            RectangleList<int> offsetList (r);
            offsetList.offsetAll (xOffset, yOffset);
            EdgeTable e2 (offsetList);
            edgeTable->edgeTable.clipToEdgeTable (e2);
            return ! edgeTable->edgeTable.isEmpty();
        }
        //====================================================================
        bool excludeClipRectangle (const juce::Rectangle<int>& r) throw()
        {
            dupeEdgeTableIfMultiplyReferenced();
            edgeTable->edgeTable.excludeRectangle (r.translated (xOffset, yOffset));
            return ! edgeTable->edgeTable.isEmpty();
        }
        //====================================================================
        void clipToPath (const Path& p, const AffineTransform& transform) throw()
        {
            dupeEdgeTableIfMultiplyReferenced();
            EdgeTable et (edgeTable->edgeTable.getMaximumBounds(), p, transform.translated ((float) xOffset, (float) yOffset));
            edgeTable->edgeTable.clipToEdgeTable (et);
        }
        //====================================================================
        void clipToImageAlpha (const Image& image, const juce::Rectangle<int>& srcClip, const AffineTransform& t) throw()
        {
        }
        //====================================================================
        void fillRect (Image& image, EdgeTable& et, const bool replaceContents = false) throw()
        {
            et.clipToEdgeTable (edgeTable->edgeTable);
            Image::BitmapData destData (image, 0, 0, image.getWidth(), image.getHeight(), 
                                        Image::BitmapData::ReadWriteMode::readWrite);
            if (fillType.isGradient())
            {
                jassert (!replaceContents); // that option is just for solid colours
            }
            else
            if (fillType.isTiledImage())
            {
                renderImage (image, fillType.image, fillType.image.getBounds(), fillType.transform, &et);
            }
            else // isColour()
            {
                const int w = image.getWidth();
                const int h = image.getHeight();
                agg::rendering_buffer buffer;
                const Image::BitmapData destData (image, 0, 0, w, h, 
                                                  Image::BitmapData::ReadWriteMode::readWrite);
                if (image.getFormat() == Image::RGB)
                    buffer.attach (destData.getPixelPointer (0,0), w, h, w * 3);
                else
                    buffer.attach (destData.getPixelPointer (0,0), w, h, w * 4);
                juce::Rectangle <int> srcClip = et.getMaximumBounds();
                agg::path_storage clip;
                clip.move_to (srcClip.getX(),        srcClip.getY());
                clip.line_to (srcClip.getRight(),    srcClip.getY());
                clip.line_to (srcClip.getRight(),    srcClip.getBottom());
                clip.line_to (srcClip.getX(),        srcClip.getBottom());
                clip.close_polygon ();
                agg::trans_affine mxt;
                dumpPath (buffer, clip, mxt, fillType.colour);
            }
        }
        //====================================================================
        void fillPath (Image& image, 
                       const Path& path, 
                       const AffineTransform& transform)
        {
            const int w = image.getWidth();
            const int h = image.getHeight();
            agg::rendering_buffer buffer;
            const Image::BitmapData destData (image, 0, 0, w, h, 
                                              Image::BitmapData::ReadWriteMode::readWrite);
            if (image.getFormat() == Image::RGB)
                buffer.attach (destData.getPixelPointer (0,0), w, h, w * 3);
            else
                buffer.attach (destData.getPixelPointer (0,0), w, h, w * 4);
            agg::path_storage p = writePath (path);
            agg::trans_affine mxt = writeAffineTransform (transform);
            dumpPath (buffer, p, mxt, fillType.colour);
        }
        //====================================================================
        //====================================================================
        void renderImage (Image& destImage, const Image& sourceImage, const juce::Rectangle<int>& srcClip,
                          const AffineTransform& t, const EdgeTable* const tiledFillClipRegion) throw()
        {
            const AffineTransform transform (t.translated ((float) xOffset, (float) yOffset));
            agg::rendering_buffer buffer;
            agg::rendering_buffer img_buffer;
            const int x = srcClip.getX();
            const int y = srcClip.getY();
            int w = srcClip.getWidth();
            int h = srcClip.getHeight();
            const Image::BitmapData srcData (sourceImage, x, y, w, h);
            if (sourceImage.getFormat() == Image::RGB)
                img_buffer.attach (srcData.getPixelPointer (0,0), w, h, w * 3);
            else
                img_buffer.attach (srcData.getPixelPointer (0,0), w, h, w * 4);
            w = destImage.getWidth();
            h = destImage.getHeight();
            const Image::BitmapData destData (destImage, 0, 0, w, h, 
                                              Image::BitmapData::ReadWriteMode::readWrite);
            if (destImage.getFormat() == Image::RGB)
                buffer.attach (destData.getPixelPointer (0,0), w, h, w * 3);
            else
                buffer.attach (destData.getPixelPointer (0,0), w, h, w * 4);
            dumpImage (buffer, img_buffer, writeAffineTransform(transform));
        }
        //====================================================================
        class EdgeTableHolder : public ReferenceCountedObject
        {
            public:
                EdgeTableHolder (const EdgeTable& e) throw() 
                    : edgeTable (e) 
                {}
                EdgeTable edgeTable;
        };
        //====================================================================
        ReferenceCountedObjectPtr <EdgeTableHolder> edgeTable;
        //====================================================================
        int xOffset, yOffset;
        Font font;
        FillType fillType;
        Graphics::ResamplingQuality interpolationQuality;
        //====================================================================
    private:
        const AGGSavedState& operator= (const AGGSavedState&);
        //====================================================================
        void dupeEdgeTableIfMultiplyReferenced() throw()
        {
            if (edgeTable->getReferenceCount() > 1)
                edgeTable = new EdgeTableHolder (edgeTable->edgeTable);
        }
        //====================================================================
};
//============================================================================
LowLevelGraphicsAGGContext::LowLevelGraphicsAGGContext (Image* imageToRenderOn)
    : image (imageToRenderOn)
{
    currentState = new AGGSavedState (image->getBounds(), 
                                      0, 0, Font(), FillType(), 
                                      Graphics::mediumResamplingQuality);
}
//=============================================================================
LowLevelGraphicsAGGContext::~LowLevelGraphicsAGGContext ()
{
}
//=============================================================================
float LowLevelGraphicsAGGContext::getScaleFactor ()
{
    return 1.0f;
}
//=============================================================================
void LowLevelGraphicsAGGContext::addTransform (const AffineTransform &)
{
}
//=============================================================================
void LowLevelGraphicsAGGContext::endTransparencyLayer ()
{
}
//=============================================================================
void LowLevelGraphicsAGGContext::clipToImageAlpha (const Image &, const AffineTransform &)
{
}
//=============================================================================
void LowLevelGraphicsAGGContext::beginTransparencyLayer (float)
{
}
//=============================================================================
void LowLevelGraphicsAGGContext::drawImage (const Image &, const AffineTransform &, bool)
{
}
//=============================================================================
bool LowLevelGraphicsAGGContext::isVectorDevice () const
{
    return false;
}
//=============================================================================
float LowLevelGraphicsAGGContext::getPhysicalPixelScaleFactor ()
{
    return 1.0f;
}
//=============================================================================
void LowLevelGraphicsAGGContext::setOrigin (Point<int> pt)
{
    currentState->xOffset += pt.getX();
    currentState->yOffset += pt.getY();
}
//=============================================================================
void LowLevelGraphicsAGGContext::setOrigin (int x, int y)
{
    currentState->xOffset += x;
    currentState->yOffset += y;
}
//=============================================================================
bool LowLevelGraphicsAGGContext::clipToRectangle (const juce::Rectangle<int>& r)
{
    return currentState->clipToRectangle (r);
}
//=============================================================================
bool LowLevelGraphicsAGGContext::clipToRectangleList (const RectangleList<int>& clipRegion)
{
    return currentState->clipToRectangleList (clipRegion);
}
//=============================================================================
void LowLevelGraphicsAGGContext::excludeClipRectangle (const juce::Rectangle<int>& r)
{
    currentState->excludeClipRectangle (r);
}
//=============================================================================
void LowLevelGraphicsAGGContext::clipToPath (const Path& path, const AffineTransform& transform)
{
    currentState->clipToPath (path, transform);
}
//=============================================================================
void LowLevelGraphicsAGGContext::clipToImageAlpha (const Image& sourceImage, const juce::Rectangle<int>& srcClip, const AffineTransform& transform)
{
    currentState->clipToImageAlpha (sourceImage, srcClip, transform);
}
//=============================================================================
bool LowLevelGraphicsAGGContext::clipRegionIntersects (const juce::Rectangle<int>& r)
{
    return currentState->edgeTable->edgeTable.getMaximumBounds().intersects (r.translated (
           currentState->xOffset, 
           currentState->yOffset));
}
//=============================================================================
juce::Rectangle<int> LowLevelGraphicsAGGContext::getClipBounds() const
//const juce::Rectangle<int> LowLevelGraphicsAGGContext::getClipBounds () const
{
    return currentState->edgeTable->edgeTable.getMaximumBounds().translated (
          -currentState->xOffset, 
          -currentState->yOffset);
}
//=============================================================================
bool LowLevelGraphicsAGGContext::isClipEmpty () const
{
    return currentState->edgeTable->edgeTable.isEmpty();
}
//==============================================================================
void LowLevelGraphicsAGGContext::saveState ()
{
    stateStack.add (new AGGSavedState (*currentState));
}
//==============================================================================
void LowLevelGraphicsAGGContext::restoreState ()
{
    AGGSavedState* const top = stateStack.getLast();
    if (top != 0)
    {
        currentState = top;
        stateStack.removeLast (1, false);
    }
    else
    {
        jassertfalse // trying to pop with an empty stack!
    }
}
//==============================================================================
void LowLevelGraphicsAGGContext::setFill (const FillType& fillType)
{
    currentState->fillType = fillType;
}
//==============================================================================
void LowLevelGraphicsAGGContext::setOpacity (float newOpacity)
{
    currentState->fillType.setOpacity (newOpacity);
}
//==============================================================================
void LowLevelGraphicsAGGContext::setInterpolationQuality (Graphics::ResamplingQuality quality)
{
    currentState->interpolationQuality = quality;
}
//==============================================================================
void LowLevelGraphicsAGGContext::fillRect (const juce::Rectangle<float>& r)
{
}
//==============================================================================
void LowLevelGraphicsAGGContext::fillRectList (const RectangleList<float>& rl)
{
}
//==============================================================================
void LowLevelGraphicsAGGContext::fillRect (const juce::Rectangle<int>& r, bool replaceExistingContents)
{
    const juce::Rectangle<int>& totalClip = currentState->edgeTable->edgeTable.getMaximumBounds();
    const juce::Rectangle<int> clipped (totalClip.getIntersection (r.translated (currentState->xOffset, currentState->yOffset)));
    if (!clipped.isEmpty())
    {
        EdgeTable et (clipped);
        currentState->fillRect (*image, et, replaceExistingContents);
    }
}
//==============================================================================
void LowLevelGraphicsAGGContext::fillPath (const Path& path, const AffineTransform& transform)
{
    EdgeTable et (currentState->edgeTable->edgeTable.getMaximumBounds(),
                  path, transform.translated ((float) currentState->xOffset,
                                              (float) currentState->yOffset));
    currentState->fillPath (*image, path, transform.translated ((float) currentState->xOffset,
                                                                (float) currentState->yOffset));
}
//==============================================================================
void LowLevelGraphicsAGGContext::drawImage (const Image&, const AffineTransform&)
{
}
//==============================================================================
void LowLevelGraphicsAGGContext::drawImage (const Image &sourceImage, 
                                            const juce::Rectangle<int> &srcClip, 
                                            const AffineTransform &transform, 
                                            bool fillEntireClipAsTiles)
{
    jassert (sourceImage.getBounds().contains (srcClip));
    currentState->renderImage (*image, sourceImage, srcClip, transform,
                               fillEntireClipAsTiles ? &(currentState->edgeTable->edgeTable) : 0);
}
//==============================================================================
void LowLevelGraphicsAGGContext::drawLine (const Line <float>& line)
{
    Line <float> halfPx ((float) line.getStartX(), 
                         (float) floor(line.getStartY() + 0.5), 
                         (float) line.getEndX(), 
                         (float) floor(line.getEndY()+ 0.5));
    Path p;
    p.addLineSegment (halfPx, 1.0f);
    fillPath (p, AffineTransform::identity);
}
//==============================================================================
void LowLevelGraphicsAGGContext::drawLine (double x1, double y1, double x2, double y2)
{
    Line <float> line ((float) x1, (float) floor(y1 + 0.5), (float) x2, (float) floor(y2+ 0.5));
    Path p;
    p.addLineSegment (line, 1.0f);
    fillPath (p, AffineTransform::identity);
}
//==============================================================================
void LowLevelGraphicsAGGContext::drawVerticalLine (int x, float top, float bottom)
{
    if (bottom > top)
    {
        Path p;
        p.startNewSubPath (x + currentState->xOffset, floor(top + currentState->yOffset + 0.5));
        p.lineTo (x + currentState->xOffset, floor(bottom + currentState->yOffset + 0.5));
        fillPath (p, AffineTransform::identity);        
    }
}
//==============================================================================
void LowLevelGraphicsAGGContext::drawVerticalLine (int x, double top, double bottom)
{
    if (bottom > top)
    {
        Path p;
        p.startNewSubPath (x + currentState->xOffset, floor(top + currentState->yOffset + 0.5));
        p.lineTo (x + currentState->xOffset, floor(bottom + currentState->yOffset + 0.5));
        fillPath (p, AffineTransform::identity);        
    }
}
//==============================================================================
void LowLevelGraphicsAGGContext::drawHorizontalLine (int y, float left, float right)
{
    if (right > left)
    {
        Path p;
        p.startNewSubPath (left + currentState->xOffset, floor(y + currentState->yOffset + 0.5));
        p.lineTo (right + currentState->xOffset, floor(y + currentState->yOffset + 0.5));
        fillPath (p, AffineTransform::identity);
    }
}
//==============================================================================
void LowLevelGraphicsAGGContext::drawHorizontalLine (int y, double left, double right)
{
    if (right > left)
    {
        Path p;
        p.startNewSubPath (left + currentState->xOffset, floor(y + currentState->yOffset + 0.5));
        p.lineTo (right + currentState->xOffset, floor(y + currentState->yOffset + 0.5));
        fillPath (p, AffineTransform::identity);
    }
}
//==============================================================================
class GlyphCache  : private DeletedAtShutdown
{
    public:
        GlyphCache() throw()
            : accessCounter (0), 
              hits (0), 
              misses (0)
        {
            for (int i = 120; --i >= 0;)
                glyphs.add (new CachedGlyph());
        }
        //==============================================================================
        ~GlyphCache() throw()
        {
            clearSingletonInstance();
        }
        //==============================================================================
        juce_DeclareSingleton_SingleThreaded_Minimal (GlyphCache);
        //==============================================================================
        void drawGlyph (AGGSavedState& state, 
                        Image& image, 
                        const Font& font, 
                        const int glyphNumber, 
                        float x, float y) throw()
        {
            ++accessCounter;
            int oldestCounter = std::numeric_limits<int>::max();
            CachedGlyph* oldest = 0;
            for (int i = glyphs.size(); --i >= 0;)
            {
                CachedGlyph* const glyph = glyphs.getUnchecked (i);
                if (glyph->glyph == glyphNumber && glyph->font == font)
                {
                    ++hits;
                    glyph->lastAccessCount = accessCounter;
                    glyph->draw (state, image, x, y);
                    return;
                }
                if (glyph->lastAccessCount <= oldestCounter)
                {
                    oldestCounter = glyph->lastAccessCount;
                    oldest = glyph;
                }
            }
            if (hits + ++misses > (glyphs.size() << 4))
            {
                if (misses * 2 > hits)
                {
                    for (int i = 32; --i >= 0;)
                        glyphs.add (new CachedGlyph());
                }
                hits = misses = 0;
                oldest = glyphs.getLast();
            }
            jassert (oldest != 0);
            oldest->lastAccessCount = accessCounter;
            oldest->generate (font, glyphNumber);
            oldest->draw (state, image, x, y);
        }
        //==============================================================================
        class CachedGlyph
        {
        public:
            CachedGlyph() : glyph (0), lastAccessCount (0) {}
            ~CachedGlyph()  {}
            void draw (AGGSavedState& state, Image& image, const float x, const float y) const throw()
            {
                //if (edgeTable != 0)
                {
                    //EdgeTable et (*edgeTable);
                    //et.translate (x, roundToInt (y));
                    //state.fillEdgeTable (image, et, false);
                    state.fillPath (image, currentPath, AffineTransform::identity.translated (x, y));
                }
            }
            void generate (const Font& newFont, const int glyphNumber) throw()
            {
                font = newFont;
                glyph = glyphNumber;
                edgeTable = 0;
                Path glyphPath;
                font.getTypeface()->getOutlineForGlyph (glyphNumber, glyphPath);
                if (! glyphPath.isEmpty())
                {
                    const float fontHeight = font.getHeight();
                    const AffineTransform transform (AffineTransform::scale (fontHeight * font.getHorizontalScale(), fontHeight)
                                                                     .translated (0.0f, -0.5f));
                    /*
                    edgeTable = new EdgeTable (glyphPath.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0),
                                                 glyphPath, transform);
                    */
                    currentPath = glyphPath;
                    currentPath.applyTransform (transform);
                }
            }
            int glyph, lastAccessCount;
            Font font;
            //==============================================================================
            juce_UseDebuggingNewOperator
        private:
            ScopedPointer <EdgeTable> edgeTable;
            Path currentPath;
            CachedGlyph (const CachedGlyph&);
            const CachedGlyph& operator= (const CachedGlyph&);
        };
        //==============================================================================
        juce_UseDebuggingNewOperator
    private:
        OwnedArray <CachedGlyph> glyphs;
        int accessCounter, hits, misses;
        GlyphCache (const GlyphCache&);
        const GlyphCache& operator= (const GlyphCache&);
};
//=============================================================================
juce_ImplementSingleton_SingleThreaded (GlyphCache);
//=============================================================================
void LowLevelGraphicsAGGContext::setFont (const Font& newFont)
{
    currentState->font = newFont;
}
//=============================================================================
const Font& LowLevelGraphicsAGGContext::getFont()
{
    return currentState->font;
}
//=============================================================================
void LowLevelGraphicsAGGContext::drawGlyph (int glyphNumber, const AffineTransform& transform)
{
    Font& f = currentState->font;
    if (transform.isOnlyTranslation())
    {
        GlyphCache::getInstance()->drawGlyph (*currentState, *image, f, glyphNumber,
                                              transform.getTranslationX() + (float) currentState->xOffset,
                                              transform.getTranslationY() + (float) currentState->yOffset);
    }
    else
    {
        Path p;
        f.getTypeface()->getOutlineForGlyph (glyphNumber, p);
        fillPath (p, AffineTransform::scale (f.getHeight() * f.getHorizontalScale(), f.getHeight()).followedBy (transform));
    }
}
//=============================================================================
bool LowLevelGraphicsAGGContext::drawTextLayout (const AttributedString& as, const Rectangle<float>& rec)
{
    Graphics g (*this);
    g.drawText ( as.getText(), 
                (int)rec.getX(), (int)rec.getY(), (int)rec.getWidth(), (int)rec.getHeight(), 
                 as.getJustification(), 
                 false );
    return false;
}
//=============================================================================

This is not a fully implemented class but work for common vectorial use.

A very simple use of this class is to work with a Image in memory, a theorical code :

{
​    Image img (Image::ARGB, 800, 600, true);
    LowLevelGraphicsAGGRenderer renderer ( &img );
    Graphics g ( renderer );

    g.fillAll ( Colours::black );
    g.setColour ( Colours::white );
    g.fillRect ( 100, 100, 600, 400 );
}

Another way to use the renderer is to write a derivated version of the CachedComponentImage class.

AGGCachedComponentImage.h

//=============================================================================
#ifndef __AGG_CACHED_COMPONENT_IMAGE_H__
#define __AGG_CACHED_COMPONENT_IMAGE_H__
//=============================================================================
#include "JuceHeader.h"
#include "LowLevelGraphicsAGGRenderer.h"
//=============================================================================
class AGGCachedComponentImage : public juce::CachedComponentImage
{
    public:
        AGGCachedComponentImage ( Component& c ) noexcept
            : owner ( c ),
              scale ( 1.0f )
        {}
        //---------------------------------------------------------------------
        LowLevelGraphicsAGGContext* getContext ()
        { 
            const juce::Rectangle<int> compBounds (owner.getLocalBounds());
            if (image.isNull() || image.getBounds() != compBounds)
            {
                image = juce::Image (owner.isOpaque()   ? Image::RGB
                                                        : Image::ARGB,
                                       jmax (1, compBounds.getWidth()),
                                       jmax (1, compBounds.getHeight()),
                                       ! owner.isOpaque());
            }
            return new LowLevelGraphicsAGGContext(&image); 
        }
        //---------------------------------------------------------------------
        void paint (Graphics& g) override
        {
            scale = g.getInternalContext().getPhysicalPixelScaleFactor();
            const juce::Rectangle<int> compBounds (owner.getLocalBounds());
            const juce::Rectangle<int> imageBounds (compBounds * scale);
            if (image.isNull() || image.getBounds() != imageBounds)
            {
                image = juce::Image (owner.isOpaque()   ? Image::RGB
                                                        : Image::ARGB,
                                       jmax (1, imageBounds.getWidth()),
                                       jmax (1, imageBounds.getHeight()),
                                       ! owner.isOpaque());
                validArea.clear();
            }

            {
                LowLevelGraphicsAGGContext lg (&image)
                juce::Graphics imG (lg);
                for (const juce::Rectangle<int>* i = validArea.begin(), * const e = validArea.end(); i != e; ++i)
                    lg.excludeClipRectangle (*i);
                if (! lg.isClipEmpty())
                {
                    if (! owner.isOpaque())
                    {
                        lg.setFill (juce::Colours::transparentBlack);
                        lg.fillRect (imageBounds, true);
                        lg.setFill (juce::Colours::black);
                    }
                    lg.addTransform (juce::AffineTransform::scale (scale));
                    owner.paintEntireComponent (imG, true);
                }
            }
            validArea = imageBounds;
            g.setColour (juce::Colours::black.withAlpha (owner.getAlpha()));
            g.drawImageTransformed (image, juce::AffineTransform::scale (compBounds.getWidth()  / (float) imageBounds.getWidth(),
                                                                         compBounds.getHeight() / (float) imageBounds.getHeight()), false);
        }
        //---------------------------------------------------------------------
        bool invalidateAll() override                                { validArea.clear(); return true; }
        bool invalidate (const juce::Rectangle<int>& area) override { validArea.subtract (area * scale); return true; }
        void releaseResources() override                            { image = juce::Image::null; }
        //---------------------------------------------------------------------
    private:
        juce::Image image;
        juce::RectangleList<int> validArea;
        juce::Component& owner;
        float scale;
        //---------------------------------------------------------------------
        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AGGCachedComponentImage)
        //---------------------------------------------------------------------
};
//=============================================================================
#endif // __AGG_CACHED_COMPONENT_IMAGE_H__
//=============================================================================

And use it "nativaly" inside your JUCE GUI by setting this class on your top-level component constructor, like this :

setCachedComponentImage ( new AGGCachedComponentImage (*this) );

Cairo is another Graphic Library with a similar quality than AGG with the major difference come's for very tiny size where AGG seem to have better mathematical precision in is algorithms (it is the same for rasterizing vectors). Cairo is a madness to compile statically (I talk for my main platform Win32), a mountain of depencies and conflicts/collisions with JUCE ( libpng, zlib ... ), so maybe for those who want to skip this "tempus fugit" work you can find a precompiled cairo library for win32 (old version cairo 1.9.1 - actual release is 1.12.16) in debug and release, with the few includes you need to work with in my dropbox : https://www.dropbox.com/sh/22whufq9680p75r/TYgzmZlVkX

Cairo advantage is that is not a flexible pipeline engine like AGG, but a surface oriented framework that allow to switch beetween various surface output. A cairo surface is equivalent to the low level renderer class of JUCE. JUCE have a PS (Postscript) output (not fully implemented but correct) and cairo too. But you can output on PDF, SVG, PNG, native surfaces ... using the same drawing methodology ofcourse. See : http://cairographics.org/manual/

Native surfaces are interesting for the printing question, for example, the win32 surfaces (GDI+) includes a CAIRO_SURFACE_TYPE_WIN32_PRINTING, that output the graphics to the printer ofcourse. For iOS, cairo allow to draw directly in Quartz surface that it's the one to use for printing too.

This following class it's obsolete, to use it you need to adjust a lot of LowLevelGraphicsContext method ( a lot of good refactoring since i've write this class ), but for explanation reason you can see how to interact with cairo and JUCE. 


LowLevelCairoRenderer.h

// =============================================================================
#ifndef __LOWLEVELCAIRORENDERER_H_684320EB__
#define __LOWLEVELCAIRORENDERER_H_684320EB__
// =============================================================================
#include "includes.h"
// =============================================================================
enum CAIRO_OUTPUT
{
    CAIRO_OUTPUT_PDF = 0,
    CAIRO_OUTPUT_PS,
    CAIRO_OUTPUT_SVG
};
// =============================================================================
class LowLevelCairoRenderer : public LowLevelGraphicsContext
{
    public:
        LowLevelCairoRenderer (CAIRO_OUTPUT output, 
                               const char* filename, 
                               unsigned int width, unsigned int height, 
                               unsigned int dpi = 300);
        LowLevelCairoRenderer (unsigned char* data,
                               unsigned int width, unsigned int height,
                               unsigned int stride, 
                               unsigned int dpi = 300);
#ifdef JUCE_WIN32
        LowLevelCairoRenderer (HDC printerHandle, 
                               unsigned int width, unsigned int height, 
                               unsigned int dpi = 300);
#elif  JUCE_MAC
        LowLevelCairoRenderer (CGContextRef quartzHandle, 
                               unsigned int width, unsigned int height, 
                               unsigned int dpi = 300);
#endif
        ~LowLevelCairoRenderer ();
        // =====================================================================
        void nextPage (bool clearSurface);
        // =====================================================================
        virtual bool isVectorDevice() const { return true; }
        //==============================================================================
        virtual void setOrigin (int x, int y);
        virtual void addTransform (const AffineTransform& transform);
        virtual float getScaleFactor();
        //==============================================================================
        virtual bool clipToRectangle (const juce::Rectangle<int>& r);
        virtual bool clipToRectangleList (const juce::RectangleList& clipRegion);
        virtual void excludeClipRectangle (const juce::Rectangle<int>& r);
        virtual void clipToPath (const Path& path, const AffineTransform& transform);
        virtual void clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform);
        //==============================================================================
        virtual bool clipRegionIntersects (const juce::Rectangle<int>& r);
        virtual juce::Rectangle<int> getClipBounds() const;
        virtual bool isClipEmpty() const;
        //==============================================================================
        virtual void saveState();
        virtual void restoreState();
        //==============================================================================
        virtual void beginTransparencyLayer (float opacity);
        virtual void endTransparencyLayer();
        //==============================================================================
        virtual void setFill (const FillType& fillType);
        virtual void setOpacity (float newOpacity);
        virtual void setInterpolationQuality (Graphics::ResamplingQuality quality);
        //==============================================================================
        virtual void fillRect (const juce::Rectangle<int>& r, bool replaceExistingContents);
        virtual void fillPath (const Path& path, const AffineTransform& transform);
        //==============================================================================
        virtual void drawImage (const Image& sourceImage, const AffineTransform& transform, 
                                bool fillEntireClipAsTiles);
        //==============================================================================
        virtual void drawLine (const Line <float>& line);
        virtual void drawVerticalLine (int x, float top, float bottom);
        virtual void drawHorizontalLine (int y, float left, float right);
        //==============================================================================
        virtual void setFont (const Font& newFont);
        virtual const juce::Font& getFont ();
        virtual void drawGlyph (int glyphNumber, const AffineTransform& transform);
        //==============================================================================
    private:
        cairo_t *cr;
        cairo_surface_t *surface;
        cairo_matrix_t *matrix;
        cairo_filter_t filter;
        cairo_glyph_t *glyphs;
        cairo_text_extents_t *extents;
        Font font;
        float opacity;
        // ====================================================================
        LowLevelCairoRenderer (const LowLevelCairoRenderer& other);
        const LowLevelCairoRenderer& operator= (const LowLevelCairoRenderer&);
        // ====================================================================
};
// =============================================================================
#endif  // __LOWLEVELCAIRORENDERER_H_684320EB__
// =============================================================================


LowLevelCairoRenderer.cpp

// ===========================================================================
#ifdef __MAC__
    #define CAIRO_HAS_QUARTZ_SURFACE 1
    #include "cairo-quartz.h"
#endif
// ===========================================================================
#include "LowLevelCairoRenderer.h"
// ===========================================================================
LowLevelCairoRenderer::LowLevelCairoRenderer (unsigned char* data,
                                              unsigned int width, 
                                              unsigned int height,
                                              unsigned int stride,
                                              unsigned int dpi)
{
    cairo_debug_reset_static_data ();
    surface = cairo_image_surface_create_for_data (data, 
                                                   CAIRO_FORMAT_ARGB32, 
                                                   width, height, stride);
    cairo_surface_set_fallback_resolution (surface, dpi, dpi);
    cr = cairo_create (surface);
    cairo_set_antialias (cr, CAIRO_ANTIALIAS_SUBPIXEL);
}
// ===========================================================================
LowLevelCairoRenderer::LowLevelCairoRenderer (CAIRO_OUTPUT output,
                                              const char *filename, 
                                              unsigned int width, 
                                              unsigned int height,
                                              unsigned int dpi)
{
    cairo_debug_reset_static_data ();
    switch (output)
    {
        case CAIRO_OUTPUT_PDF:
            surface = cairo_pdf_surface_create (filename, width, height);
            break;
        case CAIRO_OUTPUT_PS:
            surface = cairo_ps_surface_create (filename, width, height);
            break;
        case CAIRO_OUTPUT_SVG:
            surface = cairo_svg_surface_create (filename, width, height);
            break;
        default:
            surface = NULL;
            break;
    }
    cairo_surface_set_fallback_resolution (surface, dpi, dpi);
    cr = cairo_create (surface);
    cairo_set_antialias (cr, CAIRO_ANTIALIAS_SUBPIXEL);
}
//=============================================================================
#ifdef JUCE_WIN32
//=============================================================================
LowLevelCairoRenderer::LowLevelCairoRenderer (HDC printerHandle, 
                                              unsigned int /*width*/, 
                                              unsigned int /*height*/,
                                              unsigned int dpi)
{
    cairo_debug_reset_static_data ();
    surface = cairo_win32_printing_surface_create (printerHandle);
    cairo_surface_set_fallback_resolution (surface, dpi, dpi);
    cr = cairo_create (surface);
    cairo_set_antialias (cr, CAIRO_ANTIALIAS_SUBPIXEL);
}
//=============================================================================
#elif JUCE_MAC
//=============================================================================
LowLevelCairoRenderer::LowLevelCairoRenderer (CGContextRef quartzHandle,
                                              unsigned int width,
                                              unsigned int height,
                                              unsigned int dpi)
{
    cairo_debug_reset_static_data ();
    surface = cairo_quartz_surface_create_for_cg_context (quartzHandle, width, height);
    cairo_surface_set_fallback_resolution (surface, dpi, dpi);
    cr = cairo_create (surface);
    cairo_set_antialias(cr, CAIRO_ANTIALIAS_SUBPIXEL);
}
//=============================================================================
#endif
//=============================================================================
LowLevelCairoRenderer::~LowLevelCairoRenderer ()
{
    cairo_destroy (cr);
    cairo_surface_flush (surface);
    cairo_surface_destroy (surface);
}
//=============================================================================
void LowLevelCairoRenderer::nextPage (bool clearSurface)
{
    if (clearSurface)
        cairo_show_page (cr);
    else
        cairo_copy_page (cr);
}
// ============================================================================
static cairo_matrix_t* getMatrix (const AffineTransform& at)
{
    cairo_matrix_t *matrix = nullptr;
    cairo_matrix_init (matrix, 
                       at.mat00, at.mat10, 
                       at.mat01, at.mat11, 
                       at.mat02, at.mat12);
    return matrix;
}
// ============================================================================
static void drawPath (cairo_t *cr, const Path& path, const AffineTransform& transform)
{
    Path mpath (path);
    mpath.applyTransform (transform);
    Path::Iterator i (mpath);
    while (i.next())
    {
        switch (i.elementType)
        {
            case Path::Iterator::startNewSubPath:
                cairo_new_sub_path (cr);
                cairo_move_to (cr, i.x1, i.y1);
                break;
            case Path::Iterator::lineTo:
                cairo_line_to (cr, i.x1, i.y1);
                break;
            case Path::Iterator::quadraticTo:
                cairo_curve_to (cr, i.x1, i.y1, i.x2, i.y2, i.x2, i.y2);
                break;
            case Path::Iterator::cubicTo:
                cairo_curve_to (cr, i.x1, i.y1, i.x2, i.y2, i.x3, i.y3);
                break;
            case Path::Iterator::closePath:
                cairo_close_path (cr);
                break;
            default:
                jassertfalse
                break;
        }
    }
}
//=============================================================================
void LowLevelCairoRenderer::setOrigin (int x, int y)
{
    cairo_surface_set_device_offset (surface, x, y);
}
// ============================================================================
void LowLevelCairoRenderer::addTransform (const AffineTransform& transform)
{
    cairo_transform (cr, getMatrix (transform));
}
// ============================================================================
float LowLevelCairoRenderer::getScaleFactor()
{
    return 1.0f;
}
// ============================================================================
static juce::Rectangle<int> getBounds (cairo_t *cr)
{
    RectangleList bounds;
    cairo_rectangle_list_t *list = cairo_copy_clip_rectangle_list (cr);
    for (int i=0;i<list->num_rectangles;++i)
    {
        cairo_rectangle_t rect = list->rectangles[i];
        bounds.add (juce::Rectangle<int>(roundToIntAccurate(rect.x), 
                                         roundToIntAccurate(rect.y), 
                                         roundToIntAccurate(rect.width), 
                                         roundToIntAccurate(rect.height)));
    }
    return bounds.getBounds ();
}
// ============================================================================
bool LowLevelCairoRenderer::clipToRectangle (const juce::Rectangle<int>& r)
{
    cairo_reset_clip (cr);
    cairo_rectangle (cr, r.getX(), r.getY(), r.getWidth(), r.getHeight());
    cairo_clip (cr);
    return (cairo_status (cr) == CAIRO_STATUS_SUCCESS) ? true : false;
}
// ============================================================================
bool LowLevelCairoRenderer::clipToRectangleList (const RectangleList& /*clipRegion*/)
{
    return true;
}
// ============================================================================
void LowLevelCairoRenderer::excludeClipRectangle (const juce::Rectangle<int>& /*r*/)
{
}
// ============================================================================
void LowLevelCairoRenderer::clipToPath (const Path& path, const AffineTransform& transform)
{
    cairo_new_path (cr);
    drawPath (cr, path, transform);
    cairo_clip (cr);
}
// ============================================================================
void LowLevelCairoRenderer::clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform)
{
    int width = sourceImage.getWidth();
    int height = sourceImage.getHeight();
    Image::BitmapData datas (sourceImage, 0, 0, width, height);
    unsigned char* buffer = (unsigned char*)datas.getPixelPointer (0, 0);
    cairo_surface_t *image = cairo_image_surface_create_for_data 
                        (buffer, CAIRO_FORMAT_ARGB32, width, height, width*4);
    cairo_set_matrix (cr, getMatrix(transform));
    cairo_mask_surface (cr, image, 0, 0);
}
// ============================================================================
bool LowLevelCairoRenderer::clipRegionIntersects (const juce::Rectangle<int>& /*r*/)
{
    return true;
}
// ============================================================================
juce::Rectangle<int> LowLevelCairoRenderer::getClipBounds() const
{
    return getBounds(cr);
}
// ============================================================================
bool LowLevelCairoRenderer::isClipEmpty() const 
{
    return getBounds(cr).isEmpty();
}
// ============================================================================
void LowLevelCairoRenderer::saveState()
{
    cairo_save (cr);
}
// ============================================================================
void LowLevelCairoRenderer::restoreState()
{
    cairo_restore (cr);
}
// ============================================================================
void LowLevelCairoRenderer::beginTransparencyLayer (float opacity)
{
    cairo_push_group_with_content (cr, CAIRO_CONTENT_COLOR_ALPHA);
    setOpacity (opacity);
}
// ============================================================================
void LowLevelCairoRenderer::endTransparencyLayer()
{
    cairo_pop_group_to_source (cr);
}
// ============================================================================
void LowLevelCairoRenderer::setFill (const FillType& fillType)
{
    if (fillType.isColour())
    {
        Colour colour = fillType.colour;
        cairo_set_source_rgba (cr, (colour.getRed()/255.),
                                 (colour.getGreen()/255.),
                                  (colour.getBlue()/255.),
                                 (colour.getAlpha()/255.));
    }
    else
    if (fillType.isGradient())
    {
        ColourGradient *gradient = fillType.gradient;
        Point<float> p1 = gradient->point1;
        Point<float> p2 = gradient->point2;
        float radius = 10.0f;
        cairo_pattern_t *pattern;
        if (gradient->isRadial)
            pattern = cairo_pattern_create_radial (p1.getX(), p1.getY(), radius,
                                                   p2.getX(), p2.getY(), radius);
        else
            pattern = cairo_pattern_create_linear (p1.getX(), p1.getY(), 
                                                   p2.getX(), p2.getY());
        cairo_pattern_set_filter (pattern, filter);
    }
    else
    if (fillType.isTiledImage())
    {
        int width = fillType.image.getWidth();
        int height = fillType.image.getHeight();
        Image::BitmapData datas (fillType.image, 0, 0, width, height);
        unsigned char* buffer = (unsigned char*)datas.getPixelPointer (0, 0);
        cairo_surface_t *image = cairo_image_surface_create_for_data 
                            (buffer, CAIRO_FORMAT_ARGB32, width, height, width*4);
        cairo_pattern_t *pattern = cairo_pattern_create_for_surface (image);
        cairo_pattern_set_filter (pattern, filter);
        cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
        cairo_pattern_set_matrix (pattern, getMatrix (fillType.transform));
    }
    else
    if (fillType.isInvisible())
    {
    }
}
// ============================================================================
void LowLevelCairoRenderer::setOpacity (float newOpacity)
{
    opacity = newOpacity;
}
// ============================================================================
void LowLevelCairoRenderer::setInterpolationQuality (Graphics::ResamplingQuality quality) 
{
    if (quality == Graphics::lowResamplingQuality)
        filter = CAIRO_FILTER_FAST;
    else
    if (quality == Graphics::mediumResamplingQuality)
        filter = CAIRO_FILTER_GOOD;
    else
    if (quality == Graphics::highResamplingQuality)
        filter = CAIRO_FILTER_BEST;
    else
        filter = CAIRO_FILTER_GOOD;
}
// ============================================================================
void LowLevelCairoRenderer::fillRect (const juce::Rectangle<int>& r, bool replaceExistingContents) 
{
    if (replaceExistingContents)
    {
        cairo_rectangle (cr, r.getX(), r.getY(), r.getWidth(), r.getHeight());
        cairo_fill (cr);
    }
}
// ============================================================================
void LowLevelCairoRenderer::fillPath (const Path& path, const AffineTransform& transform) 
{
    cairo_new_path (cr);
    drawPath (cr, path, transform);
    cairo_fill (cr);
}
// ============================================================================
void LowLevelCairoRenderer::drawImage (const Image& sourceImage, 
                                       const AffineTransform& transform,
                                       bool fillEntireClipAsTiles)
{
    if (fillEntireClipAsTiles)
    {
        setFill(FillType(sourceImage, transform));
        cairo_fill (cr);
    }
    else
    {
        int width = sourceImage.getWidth();
        int height = sourceImage.getHeight();
        Image::BitmapData datas (sourceImage, 0, 0, width, height);
        unsigned char* buffer = (unsigned char*)datas.getPixelPointer (0, 0);
        cairo_surface_t *image = cairo_image_surface_create_for_data 
                            (buffer, CAIRO_FORMAT_ARGB32, width, height, width*4);
        cairo_set_source_surface (cr, image, 0, 0);
        cairo_paint_with_alpha (cr, opacity);
    }
}
// ============================================================================
void LowLevelCairoRenderer::drawLine (const Line <float>& line) 
{
    cairo_set_line_width (cr, 1.0);
    cairo_move_to (cr, line.getStartX(), line.getStartY());
    cairo_line_to (cr, line.getEndX(), line.getEndY());
    cairo_stroke (cr);
}
// ============================================================================
void LowLevelCairoRenderer::drawVerticalLine (int x, float top, float bottom)
{
    drawLine (Line<float>((float)x, top, (float)x, bottom));
}
// ============================================================================
void LowLevelCairoRenderer::drawHorizontalLine (int y, float left, float right)
{
    drawLine (Line<float>(left, (float)y, right, (float)y));
}
// ============================================================================
void LowLevelCairoRenderer::setFont (const Font& newFont)
{
    font = newFont;
    cairo_font_slant_t slant = font.isItalic() ? CAIRO_FONT_SLANT_NORMAL : CAIRO_FONT_SLANT_ITALIC;
    cairo_font_weight_t weight = font.isBold() ? CAIRO_FONT_WEIGHT_BOLD : CAIRO_FONT_WEIGHT_NORMAL;
    cairo_select_font_face (cr, (const char *)font.getTypefaceName().toUTF8(), slant, weight);
    cairo_set_font_size (cr, font.getHeight());
    cairo_glyph_extents (cr, glyphs, 10, extents);
}
// ============================================================================
const juce::Font& LowLevelCairoRenderer::getFont()
{
    return font;
}
// ============================================================================
void LowLevelCairoRenderer::drawGlyph (int glyphNumber, const AffineTransform& transform)
{
    int num_glyphs = 1;
    cairo_set_font_matrix (cr, getMatrix(transform));
    cairo_show_glyphs (cr, glyphs + glyphNumber, num_glyphs);
}
// ============================================================================

It's cool, but it's not really a Printing Framework, you'll need to write you're own crossplatform classes to interact with native printing windows (for settings the output). Some years ago i've write some classes based on the printing framework of WxWidgets, but nothing to share at this level, the code is really not right.

Just for a look, there's the main code for access to the native printing dialog under Windows or Mac :

juce_PageSetupDialogMac.mm

//==============================================================================
#include "mac_headers.h"
//==============================================================================
#include "juce_PrinterDefs.h"
#include "juce_PageSetupDialogMac.h"
#include "juce_PrintNativeDataMac.h"
#include "juce_PrintData.h"
#include "juce_Paper.h"
//==============================================================================
MacPageSetupDialog::MacPageSetupDialog ()
    : m_dialogParent (NULL)
{
}
//==============================================================================
MacPageSetupDialog::MacPageSetupDialog (Component *parent, 
                                        PageSetupDialogData *data)
{
    create (parent, data);
}
//==============================================================================
MacPageSetupDialog::~MacPageSetupDialog ()
{
}
//==============================================================================
bool MacPageSetupDialog::create (Component *parent, 
                                 PageSetupDialogData *data)
{
    m_dialogParent = parent;
    if (data)
        m_pageSetupData = (*data);
    return true;
}
//==============================================================================
int MacPageSetupDialog::showModal ()
{
    convertToNative(m_pageSetupData);
    //((MacPrintNativeData*)m_pageSetupData.getPrintData().getNativeData())->transferFrom (&m_pageSetupData);
    
    NSPageLayout *pageLayout = [NSPageLayout pageLayout];
    int retVal = [pageLayout runModal];
    if (retVal == NSOKButton)
    {
        convertFromNative(m_pageSetupData);
        //m_pageSetupData.setPaperSize (m_pageSetupData.getPrintData().getPaperSize());
        //((MacPrintNativeData*)m_pageSetupData.getPrintData().getNativeData())->transferTo (&m_pageSetupData);
        
        return ID_OK;
    }
    return ID_CANCEL;
}
//==============================================================================
bool MacPageSetupDialog::convertToNative (PageSetupDialogData &data)
{
    NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo];
    
    /* Paper Name */
    NSString *papName = [printInfo paperName];
    NSString *locPapName = [printInfo localizedPaperName];
    String paperName = nsStringToJuce (papName);
    String localizedPaperName = nsStringToJuce (locPapName);
    
    /* Paper Size */
    NSSize size = [printInfo paperSize];
    Rectangle paperSize (0, 0, size.width, size.height); 
    data.setPaperSize (paperSize);
    
    PaperSize id = ThePrintPaperDatabase->getSize (Rectangle (0, 0, size.width * 10, size.height * 10));
    if (id != PAPER_NONE)
        data.setPaperId (id);
    
    /* Margin */
    float bottom = [printInfo bottomMargin];
    float top = [printInfo topMargin];
    float left = [printInfo leftMargin];
    float right = [printInfo rightMargin];
    
    data.setMarginTopLeft (Point(top, left));
    data.setMarginBottomRight (Point(bottom, right));
    
    return true;
}
//==============================================================================
bool MacPageSetupDialog::convertFromNative (PageSetupDialogData &data)
{
    MacPrintNativeData *native_data =
        (MacPrintNativeData *) data.getPrintData().getNativeData();
    
    data.getPrintData().convertToNative();
    
    NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo];
    
    PaperSize id = data.getPaperId();
    /*
    // Paper Name //
    PaperSize id = data.getPaperId();
    String name = ThePrintPaperDatabase->convertIdToName(id);
    [printInfo setPaperName:juceStringToNS(name)];
    */
    /* Paper Size */
    Rectangle paperSize = ThePrintPaperDatabase->getSize (id);
    NSSize size;
    size.width = paperSize.getWidth();
    size.height = paperSize.getHeight();
    [printInfo setPaperSize:size];
    
    /* Margins */
    Point topleft = data.getMarginTopLeft();
    Point bottomright = data.getMarginBottomRight();
    [printInfo setTopMargin:topleft.getX()];
    [printInfo setLeftMargin:topleft.getY()];
    [printInfo setBottomRight:bottomright.getX()];
    [printInfo setRightMargin:bottomright.getY()];
    
    return true;
}
//==============================================================================

juce_PageSetupDialogWindows.cpp

//==============================================================================
#include "juce_PrinterDefs.h"
#include "juce_PageSetupDialogWindows.h"
#include "juce_PrintNativeDataWindows.h"
//==============================================================================
WindowsPageSetupDialog::WindowsPageSetupDialog ()
    : m_dialogParent (NULL),
      m_pageDlg (NULL)
{
}
//==============================================================================
WindowsPageSetupDialog::WindowsPageSetupDialog (Component *parent, 
                                                PageSetupDialogData *data)
{
    create (parent, data);
}
//==============================================================================
WindowsPageSetupDialog::~WindowsPageSetupDialog ()
{
    PAGESETUPDLG *pd = (PAGESETUPDLG *)m_pageDlg;
    if (pd && pd->hDevMode)
        GlobalFree (pd->hDevMode);
    if (pd && pd->hDevNames)
        GlobalFree (pd->hDevNames);
    if (pd)
        delete pd;
}
//==============================================================================
bool WindowsPageSetupDialog::create (Component *parent, 
                                     PageSetupDialogData *data)
{
    m_dialogParent = parent;
    m_pageDlg = NULL;
    if (data)
        m_pageSetupData = (*data);
    return true;
}
//==============================================================================
int WindowsPageSetupDialog::showModal ()
{
    convertToNative (m_pageSetupData);
    PAGESETUPDLG *pd = (PAGESETUPDLG *)m_pageDlg;
    if (m_dialogParent)
        pd->hwndOwner = (HWND)m_dialogParent->getWindowHandle();
    else
        pd->hwndOwner = 0;
    BOOL retVal = PageSetupDlg (pd);
    pd->hwndOwner = 0;
    if (retVal)
    {
        convertFromNative (m_pageSetupData);
        return ID_OK;
    }
    return ID_CANCEL;
}
//==============================================================================
bool WindowsPageSetupDialog::convertToNative (PageSetupDialogData &data)
{
    WindowsPrintNativeData *native_data =
        (WindowsPrintNativeData *) data.getPrintData().getNativeData();
    data.getPrintData().convertToNative();
    
    PAGESETUPDLG *pd = (PAGESETUPDLG*) m_pageDlg;
    // Shouldn't have been defined anywhere
    if (pd)
        return false;
    pd = new PAGESETUPDLG;
    pd->hDevMode = NULL;
    pd->hDevNames = NULL;
    m_pageDlg = (void *)pd;
    // Pass the devmode data (created in m_printData.ConvertToNative)
    // to the PRINTDLG structure, since it'll
    // be needed when PrintDlg is called.
    if (pd->hDevMode)
    {
        GlobalFree(pd->hDevMode);
        pd->hDevMode = NULL;
    }
    pd->hDevMode = (HGLOBAL) native_data->getDevMode();
    native_data->setDevMode( (void*) NULL );
    // Shouldn't assert; we should be able to test Ok-ness at a higher level
    //wxASSERT_MSG( (pd->hDevMode), wxT("hDevMode must be non-NULL in ConvertToNative!"));
    // Pass the devnames data (created in m_printData.ConvertToNative)
    // to the PRINTDLG structure, since it'll
    // be needed when PrintDlg is called.
    if (pd->hDevNames)
    {
        GlobalFree(pd->hDevNames);
        pd->hDevNames = NULL;
    }
    pd->hDevNames = (HGLOBAL) native_data->getDevNames();
    native_data->setDevNames((void*) NULL);
//        pd->hDevMode = GlobalAlloc(GMEM_MOVEABLE, sizeof(DEVMODE));
    pd->Flags = PSD_MARGINS|PSD_MINMARGINS;
    if ( data.getDefaultMinMargins() )
        pd->Flags |= PSD_DEFAULTMINMARGINS;
    if ( !data.getEnableMargins() )
        pd->Flags |= PSD_DISABLEMARGINS;
    if ( !data.getEnableOrientation() )
        pd->Flags |= PSD_DISABLEORIENTATION;
    if ( !data.getEnablePaper() )
        pd->Flags |= PSD_DISABLEPAPER;
    if ( !data.getEnablePrinter() )
        pd->Flags |= PSD_DISABLEPRINTER;
    if ( data.getDefaultInfo() )
        pd->Flags |= PSD_RETURNDEFAULT;
    if ( data.getEnableHelp() )
        pd->Flags |= PSD_SHOWHELP;
    // We want the units to be in hundredths of a millimetre
    pd->Flags |= PSD_INHUNDREDTHSOFMILLIMETERS;
    pd->lStructSize = sizeof( PAGESETUPDLG );
    pd->hwndOwner=(HWND)NULL;
//    pd->hDevNames=(HWND)NULL;
    pd->hInstance=(HINSTANCE)NULL;
    //   PAGESETUPDLG is in hundreds of a mm
    pd->ptPaperSize.x = data.getPaperSize().getX() * 100;
    pd->ptPaperSize.y = data.getPaperSize().getY() * 100;
    pd->rtMinMargin.left = data.getMinMarginTopLeft().getX() * 100;
    pd->rtMinMargin.top = data.getMinMarginTopLeft().getY() * 100;
    pd->rtMinMargin.right = data.getMinMarginBottomRight().getX() * 100;
    pd->rtMinMargin.bottom = data.getMinMarginBottomRight().getY() * 100;
    pd->rtMargin.left = data.getMarginTopLeft().getX() * 100;
    pd->rtMargin.top = data.getMarginTopLeft().getY() * 100;
    pd->rtMargin.right = data.getMarginBottomRight().getX() * 100;
    pd->rtMargin.bottom = data.getMarginBottomRight().getY() * 100;
    pd->lCustData = 0;
    pd->lpfnPageSetupHook = NULL;
    pd->lpfnPagePaintHook = NULL;
    pd->hPageSetupTemplate = NULL;
    pd->lpPageSetupTemplateName = NULL;
/*
    if ( pd->hDevMode )
    {
        DEVMODE *devMode = (DEVMODE*) GlobalLock(pd->hDevMode);
        memset(devMode, 0, sizeof(DEVMODE));
        devMode->dmSize = sizeof(DEVMODE);
        devMode->dmOrientation = m_orientation;
        devMode->dmFields = DM_ORIENTATION;
        GlobalUnlock(pd->hDevMode);
    }
*/
    return true;
}
//==============================================================================
bool WindowsPageSetupDialog::convertFromNative (PageSetupDialogData &data)
{
    PAGESETUPDLG *pd = (PAGESETUPDLG *) m_pageDlg;
    if ( !pd )
        return false;
    WindowsPrintNativeData *native_data =
        (WindowsPrintNativeData *) data.getPrintData().getNativeData();
    // Pass the devmode data back to the wxPrintData structure where it really belongs.
    if (pd->hDevMode)
    {
        if (native_data->getDevMode())
        {
            // Make sure we don't leak memory
            GlobalFree((HGLOBAL) native_data->getDevMode());
        }
        native_data->setDevMode( (void*) pd->hDevMode );
        pd->hDevMode = NULL;
    }
    // Isn't this superfluous? It's called again below.
    // data.GetPrintData().ConvertFromNative();
    // Pass the devnames data back to the wxPrintData structure where it really belongs.
    if (pd->hDevNames)
    {
        if (native_data->getDevNames())
        {
            // Make sure we don't leak memory
            GlobalFree((HGLOBAL) native_data->getDevNames());
        }
        native_data->setDevNames((void*) pd->hDevNames);
        pd->hDevNames = NULL;
    }
    data.getPrintData().convertFromNative();
    pd->Flags = PSD_MARGINS|PSD_MINMARGINS;
    data.setDefaultMinMargins( ((pd->Flags & PSD_DEFAULTMINMARGINS) == PSD_DEFAULTMINMARGINS) );
    data.enableMargins( ((pd->Flags & PSD_DISABLEMARGINS) != PSD_DISABLEMARGINS) );
    data.enableOrientation( ((pd->Flags & PSD_DISABLEORIENTATION) != PSD_DISABLEORIENTATION) );
    data.enablePaper( ((pd->Flags & PSD_DISABLEPAPER) != PSD_DISABLEPAPER) );
    data.enablePrinter( ((pd->Flags & PSD_DISABLEPRINTER) != PSD_DISABLEPRINTER) );
    data.setDefaultInfo( ((pd->Flags & PSD_RETURNDEFAULT) == PSD_RETURNDEFAULT) );
    data.enableHelp( ((pd->Flags & PSD_SHOWHELP) == PSD_SHOWHELP) );
    //   PAGESETUPDLG is in hundreds of a mm
    if (data.getPrintData().getOrientation() == LANDSCAPE)
        data.setPaperSize (Rectangle(0, 0, pd->ptPaperSize.y / 100, pd->ptPaperSize.x / 100));
    else
        data.setPaperSize (Rectangle(0, 0, pd->ptPaperSize.x / 100, pd->ptPaperSize.y / 100));
    data.setMinMarginTopLeft (Point(pd->rtMinMargin.left / 100, pd->rtMinMargin.top / 100));
    data.setMinMarginBottomRight (Point(pd->rtMinMargin.right / 100, pd->rtMinMargin.bottom / 100));
    data.setMarginTopLeft (Point(pd->rtMargin.left / 100, pd->rtMargin.top / 100));
    data.setMarginBottomRight (Point(pd->rtMargin.right / 100, pd->rtMargin.bottom / 100));
    return true;
}
//==============================================================================

And some code for drawing into the printer using cairo class

juce_PrintDialogMac.mm

//==============================================================================
#include "mac_headers.h"
//==============================================================================
#include "LowLevelGraphicsCairoRenderer.h"
//==============================================================================
#include "Document.h"
#include "DocumentHolder.h"
#include "DocumentLayout.h"
#include "Page.h"
//==============================================================================
#include "juce_PrintDialogMac.h"
#include "juce_PrintNativeDataMac.h"
//==============================================================================
@implementation PrintingView : NSView
-(void)beginDocument 
{
}
-(void)endDocument 
{
}
-(void)endPage
{
}
-(void)endPageSetup
{
}
// draw a rectangle into the view
-(void)drawRect:(NSRect)rect
{
    int page = 1;
    DocumentHolder *holder = DocumentHolder::getActiveDocumentHolder();
    DocumentLayout *layout = holder->getDocument()->getDocumentLayout();
    Page *actualPage = layout->getPage (page-1);
    if (page)
    {
        ModuleManager* manager = actualPage->getModuleManager();
        DrawableComposite  *composite = new DrawableComposite ();
        Path path;
        path.addRectangle (0, 0, actualPage->getPageWidth(), actualPage->getPageHeight());
        DrawablePath *drawable = new DrawablePath();
        drawable->setPath (path);
        drawable->setSolidFill (Colours::white);
        composite->insertDrawable (*drawable);
        manager->getDrawable (*composite);
        
        double width = actualPage->getPageWidth ();
        double height = actualPage->getPageHeight();
        NSPrintInfo *pi = [[NSPrintOperation currentOperation] printInfo];
        NSSize paperSize = [pi paperSize];
        Rectangle r(0, 0, paperSize.width, paperSize.height);
        LowLevelGraphicsCairoRenderer printer ((CGContextRef)[[NSGraphicsContext currentContext] graphicsPort], 
                                                r.getWidth(), r.getHeight());
         
         Graphics printerDraw (&printer);
         composite->drawWithin (printerDraw, 
         r.getX(), r.getY(), 
         roundDoubleToInt(r.getWidth()), 
         roundDoubleToInt(r.getHeight()), 
         RectanglePlacement::xMid |
         RectanglePlacement::yMid |
         RectanglePlacement::stretchToFit);
        delete drawable;
        delete composite;
    }
}
// Draw the header/footer page number
-(void)drawPageBorderWithSize:(NSSize)borderSize
{
    NSRect oldFrame = [self frame];
    [self setFrame:NSMakeRect(0, 0, borderSize.width, borderSize.height)];
    [self setFrameOrigin:NSMakePoint(0.0, 0.0)];
    [self setFrameSize:borderSize];
    [self lockFocus];
    
    // some drawing here
    
    [self unlockFocus];
    [self setFrame:oldFrame];
}
// Return the number of pages available for printing
-(BOOL)knowsPageRange:(NSRangePointer)range
{
    NSRect bounds = [self bounds];
    NSSize printSize = [self calculatePrintSize];
    
    range->location = 1;
    int horiz = NSWidth (bounds) / printSize.width + 1;
    int vert = NSHeight (bounds) / printSize.height + 1;
    range->length = horiz * vert;
    return YES;
}
// Return the drawing rectangle for a particular page number
-(NSRect)rectForPage:(int)page
{
    NSRect bounds = [self bounds];
    NSSize printSize = [self calculatePrintSize];
    
    // hpage 1 to horiz and vpage 1 to vert (horiz and vert defined above)
    int pagesForWidth = NSWidth (bounds) / printSize.width + 1;
    int vpage = page / pagesForWidth;
    int hpage = page % pagesForWidth;
    if (hpage == 0)
        hpage = pagesForWidth;
    else
        vpage++;
    
    return NSMakeRect (NSMinX(bounds)+(hpage-1)*printSize.width,
                       NSMinY(bounds)+(vpage-1)*printSize.width,
                       printSize.width,
                       printSize.height);
}
// Calculate the size of the view that fits on a single page
-(NSSize)calculatePrintSize
{
    // Obtain the print info object for the current operation
    NSPrintInfo *pi = [[NSPrintOperation currentOperation] printInfo];
    
    // Calculate the pase size in points
    NSSize paperSize = [pi paperSize];
    float pageHeight = paperSize.height - [pi topMargin] - [pi bottomMargin];
    float pageWidth = paperSize.width - [pi leftMargin] - [pi rightMargin];
    
    // Convert size to the scaled view
    float scale = [[[pi dictionary] objectForKey:NSPrintScalingFactor] floatValue];
    return NSMakeSize(pageWidth / scale, pageHeight / scale);
}
@end
//==============================================================================
MacPrintDialog::MacPrintDialog (Component *parent, PrintDialogData *data)
{
    create (parent, data);
}
//==============================================================================
MacPrintDialog::MacPrintDialog (Component *parent, PrintData *data)
{
    PrintDialogData data2;
    if (data)
        data2 = *data;
    create (parent, &data2);
}
//==============================================================================
bool MacPrintDialog::create (Component *parent, PrintDialogData *data)
{
    m_dialogParent = parent;
    m_printerDC = NULL;
    m_destroyDC = true;
    if (data)
        m_printDialogData = *data;
    return true;
}
//==============================================================================
MacPrintDialog::~MacPrintDialog ()
{
}
//==============================================================================
int MacPrintDialog::showModal ()
{
    NSPrintInfo    *printInfo = [NSPrintInfo sharedPrintInfo];
    
    PrintingView *view = [PrintingView alloc];
    NSSize vsize = [printInfo paperSize];
    //[view setBoundsSize:vsize];
    
    NSPrintOperation *op = [NSPrintOperation printOperationWithView:view printInfo:printInfo];
    [op setAccessoryView:view];
    [op runOperation];
    return ID_OK;
}
//==============================================================================
PrinterDC* MacPrintDialog::getPrintDC ()
{
    if (m_printerDC)
    {
        m_destroyDC = false;
        return m_printerDC;
    }
    else
        return (PrinterDC*)NULL;
}
//==============================================================================
bool MacPrintDialog::convertToNative (PrintDialogData &data)
{
    MacPrintNativeData *native_data =
        (MacPrintNativeData *) data.getPrintData().getNativeData();
    data.getPrintData().convertToNative();
    return true;
}
//==============================================================================
bool MacPrintDialog::convertFromNative (PrintDialogData &data)
{
    MacPrintNativeData *native_data =
        (MacPrintNativeData *) data.getPrintData().getNativeData();
    return true;
}
//==============================================================================

juce_PrintoutWindows.cpp

//==============================================================================
#include "juce_PrintoutWindows.h"
//==============================================================================
WindowsPrintout::WindowsPrintout ()
    : cairo (0)
{
    DocumentHolder *holder = DocumentHolder::getActiveDocumentHolder();
    Document *doc = holder->getDocument ();
    layout = doc->getDocumentLayout();
}
//==============================================================================
WindowsPrintout::~WindowsPrintout ()
{
}
//==============================================================================
void WindowsPrintout::onPreparePrinting ()
{
}
//==============================================================================
void WindowsPrintout::onBeginPrinting ()
{
    /*
    Page *actualPage = layout->getActivePage ();
    double width = actualPage->getPageWidth ();
    double height = actualPage->getPageHeight();
    cairo = new LowLevelGraphicsCairoRenderer (getDC()->getHDC(), width, height);
    */
}
//==============================================================================
bool WindowsPrintout::onBeginDocument (int startPage, int endPage)
{
    return getDC()->startDoc(TRANS("Printing ") + getTitle());
}
//==============================================================================
bool WindowsPrintout::hasPage (int page)
{
    Page *actualPage = layout->getPage (page-1);
    if (actualPage)
        return true;
    else
        return false;
}
//==============================================================================
bool WindowsPrintout::onPrintPage (int page)
{
    Page *actualPage = layout->getPage (page-1);
    if (page)
    {
        ModuleManager* manager = actualPage->getModuleManager();
        DrawableComposite  *composite = new DrawableComposite ();
        Path path;
        path.addRectangle (0, 0, actualPage->getPageWidth(), actualPage->getPageHeight());
        DrawablePath *drawable = new DrawablePath();
        drawable->setPath (path);
        drawable->setSolidFill (Colours::white);
        composite->insertDrawable (*drawable);
        manager->getDrawable (*composite);
        double width = actualPage->getPageWidth ();
        double height = actualPage->getPageHeight();
        HDC printerDC = getDC()->getHDC();
        Rectangle r = getDC()->getPaperRect();
        LowLevelGraphicsCairoRenderer printer (printerDC, r.getWidth(), r.getHeight());
        Graphics printerDraw (&printer);
        composite->drawWithin (printerDraw, 
                               r.getX(), r.getY(), 
                               roundDoubleToInt(r.getWidth()), 
                               roundDoubleToInt(r.getHeight()), 
                               RectanglePlacement::xMid |
                               RectanglePlacement::yMid |
                               RectanglePlacement::stretchToFit);
        delete drawable;
        delete composite;
        return true;
    }
    else
        return false;
}
//==============================================================================
void WindowsPrintout::onEndDocument ()
{
    getDC()->endDoc ();
}
//==============================================================================
void WindowsPrintout::onEndPrinting ()
{
    if (cairo)
    {
        delete cairo;
        cairo = NULL;
    }
}
//==============================================================================

As you can see, working on the question of a printing framework it's the same amount of work of the question of the RichTextEditor. But this is not a reason to forget to think about :) 

Honestly, Jules, if you do the job with the same quality (and simplicity of use) that the current classes of JUCE, your toolkit will be close to perfection with a great stability compared to the others toolkit like QT. If I want to be controversial, I would say it is in my most important sense to solve these issues, with a priority on redesigning the TextEditor, which dizzily limit the possibilities of interaction with the user (TextEditor) or require users having to use third party tools to the issue of printing. I may be alone in thinking that the paper is not dead?


In any case, I hope this information on the use of low-level renderer has been useful to more than one user of our beloved JUCE. 


friendly


#2

Wow, some interesting code in there!

I'll need to find some time to devote to having a serious look at it. Have you done any performance testing of these different implementations?


#3

Sure not, no optimisation at all, just some high level use that seem work without loose of performance with my old computer. All i can say is that AGG is not really a optimised projet, the author say that some optimisations can be add to boost the response of the pipeline but i never occur real performance problem. Maybe when i play to rotate (transform) a bitmap in fullscreen with some interpolator (like Bicubic) but animation is not the object of AGG i think. For Cairo, i don't know if the projet is low-level optimised (in the case of PDF for example, because for native surface, the optimisation is not really the question)

About the modification of the CachedComponentImage, as you can see I take instance of the LowLevelContext directly to the stack in the paint method, and for the moment, I can't see difference in performance. At this level, maybe a check with a random graphics childrens component can be quick to code to see the limitations.

Maybe i will work on that point sometimes because I use this process in my current project.


#4

awesome. is there any update on this work?


#5

#6

if anyone would like to help me on this… it’s a work in progress


#7

Not to be needlessly negative, but why? Isn’t just Cairo another level of drawing indirection redoing what JUCE’s various Graphics backings are already doing?

I would be all for this if Cairo had a reputation for being blazing fast or having cutting edge backends for things like Vulkan, but as far as I can tell it doesn’t bring anything that Graphics doesn’t already have…

EDIT: Doh, reading earlier in the thread I suppose it’s extremely handy that Cairo can do path export for things like printing. Is that the main motivation?


#8

i have my reasons :slight_smile:


#9

Fair enough!


#10

Please explain your reasons. Is it printing? Otherwise I couldn’t agree more with jonathonracz. If another graphics abstraction really is needed, it would make a lot of sense to choose one that can target the latest APIs like coregraphics, direct2d, vulcan and d3d12 as well as postscript for printing. Cairo has had a lot of promised backends, but most of them seemingly never have been completed, so it feels like the same story as Juce’s own direct2d backend.

On OSX it’s btw. already now possible to make juce/coregraphics draw to a pdf context and that can be used for printing.


#11

in my case, i am interested in porting some max msp ui objects to plugins. max’s jgraphics api is basically cairo, so it means the code can be easily transferred. Don’t really need a GraphicsContext for this, but I was just getting maxprod’s code going.