I googled http://libharu.org/ and changed the Postscript context to use it.
I can now create PDF documents with pahts and text rendered with solid brushes, I haven’t found out how to deal with images and it seems gradients aren’t well supported, the Postscript context doesn’t implement them either.
The header file:
[code]#ifndef JUCE_PDFWRITER_JUCEHEADER
#define JUCE_PDFWRITER_JUCEHEADER
#include “hpdf.h”
BEGIN_JUCE_NAMESPACE
#include “src/gui/graphics/contexts/juce_LowLevelGraphicsContext.h”
class JUCE_API PrintingContext : public LowLevelGraphicsContext
{
public:
enum Orientation
{
Portrait,
Landscape
};
enum PageSize
{
PageSize_Letter, // 8.5 x 11 " 612 x 792
PageSize_Legal, // 8.5 x 14 " 612 x 1008
PageSize_A3, // 297 x 420 mm 841.89 x 1199.551
PageSize_A4, // 210 x 297 mm 595.276 x 841.89
PageSize_A5, // 148 x 210 mm 419.528 x 595.276
PageSize_B4, // 250 x 353 mm 708.661 x 1000.63
PageSize_B5, // 176 x 250 mm 498.898 x 708.661
PageSize_Executive, // 7.25 x 10.5 " 522 x 756
PageSize_US4x6, // 4 x 6 " 288 x 432
PageSize_US4x8, // 4 x 8 " 288 x 576
PageSize_US5x7, // 5 x 7 " 360 x 504
PageSize_COMM10 // 4.125 x 9.5 " 297 x 684
};
virtual void beginPrinting() = 0;
virtual void endPrinting() = 0;
virtual Graphics *beginPage(PageSize pageSize_, Orientation orientation_) = 0;
virtual void endPage() = 0;
juce_UseDebuggingNewOperator
};
//==============================================================================
/**
An implementation of LowLevelGraphicsContext that turns the drawing operations
into a PDF document.
*/
class JUCE_API PDFWriter : public PrintingContext
{
public:
//==============================================================================
PDFWriter (OutputStream &out_, int dpi_, const bool colour_ = true);
~PDFWriter();
//==============================================================================
virtual void beginPrinting();
virtual void endPrinting();
virtual Graphics *beginPage(PageSize pageSize_, Orientation orientation_);
virtual void endPage();
bool isVectorDevice() const;
void setOrigin (int x, int y);
bool reduceClipRegion (int x, int y, int w, int h);
bool reduceClipRegion (const RectangleList& clipRegion);
void excludeClipRegion (int x, int y, int w, int h);
void saveState();
void restoreState();
bool clipRegionIntersects (int x, int y, int w, int h);
const Rectangle getClipBounds() const;
bool isClipEmpty() const;
//==============================================================================
void fillRectWithColour (int x, int y, int w, int h, const Colour& colour, const bool replaceExistingContents);
void fillRectWithGradient (int x, int y, int w, int h, const ColourGradient& gradient);
void fillPathWithColour (const Path& path, const AffineTransform& transform, const Colour& colour, EdgeTable::OversamplingLevel quality);
void fillPathWithGradient (const Path& path, const AffineTransform& transform, const ColourGradient& gradient, EdgeTable::OversamplingLevel quality);
void fillPathWithImage (const Path& path, const AffineTransform& transform,
const Image& image, int imageX, int imageY, float alpha, EdgeTable::OversamplingLevel quality);
void fillAlphaChannelWithColour (const Image& alphaImage, int imageX, int imageY, const Colour& colour);
void fillAlphaChannelWithGradient (const Image& alphaImage, int imageX, int imageY, const ColourGradient& gradient);
void fillAlphaChannelWithImage (const Image& alphaImage, int alphaImageX, int alphaImageY,
const Image& fillerImage, int fillerImageX, int fillerImageY, float alpha);
//==============================================================================
void blendImage (const Image& sourceImage, int destX, int destY, int destW, int destH,
int sourceX, int sourceY, float alpha);
void blendImageRescaling (const Image& sourceImage, int destX, int destY, int destW, int destH,
int sourceX, int sourceY, int sourceW, int sourceH,
float alpha, const Graphics::ResamplingQuality quality);
void blendImageWarping (const Image& sourceImage, int srcClipX, int srcClipY, int srcClipW, int srcClipH,
const AffineTransform& transform,
float alpha, const Graphics::ResamplingQuality quality);
//==============================================================================
void drawLine (double x1, double y1, double x2, double y2, const Colour& colour);
void drawVerticalLine (const int x, double top, double bottom, const Colour& col);
void drawHorizontalLine (const int x, double top, double bottom, const Colour& col);
//==============================================================================
juce_UseDebuggingNewOperator
protected:
//==============================================================================
RectangleList* clip;
const int dpi;
const bool colour;
OutputStream &out;
Graphics *currentGraphics;
bool needToClip;
Colour lastColour;
struct SavedState
{
SavedState (RectangleList* const clip, const int xOffset, const int yOffset);
~SavedState();
RectangleList* clip;
const int xOffset, yOffset;
private:
SavedState (const SavedState&);
const SavedState& operator= (const SavedState&);
};
OwnedArray <SavedState> stateStack;
void pageSetRGBFill(const Colour &colour);
void pageRectangle(float x, float y, float w, float h);
void pageMoveTo(float x1, float y1);
void pageCubicTo(float x1, float y1, float x2, float y2, float x3, float y3);
void pageQuadraticTo(float x1, float y1, float x2, float y2);
void pageLineTo(float x1, float y1);
void pagePath(const Path &path);
void pageFill();
void pageClip();
void pageDrawImage (const Image& im, const int sx, const int sy, const int maxW, const int maxH) const;
PDFWriter (const PDFWriter& other);
const PDFWriter& operator= (const PDFWriter&);
HPDF_Doc pdf;
HPDF_Page page;
};
END_JUCE_NAMESPACE
#endif // JUCE_PDFWRITER_JUCEHEADER
[/code]
and the implementation file:
#include "src/core/juce_StandardHeader.h"
#include "juce_PDFWriter.h"
BEGIN_JUCE_NAMESPACE
#include "src/gui/graphics/contexts/juce_EdgeTable.h"
#include "src/gui/graphics/imaging/juce_Image.h"
#include "src/gui/graphics/colour/juce_PixelFormats.h"
#include "src/gui/graphics/geometry/juce_PathStrokeType.h"
#include "src/gui/graphics/geometry/juce_Rectangle.h"
#include "src/containers/juce_SparseSet.h"
#if JUCE_MSVC
#pragma warning (disable: 4996) // deprecated sprintf warning
#endif
// this will throw an assertion if you try to draw something that's not
// possible in PDF
#define WARN_ABOUT_NON_PDF_OPERATIONS 0
//==============================================================================
#if defined (JUCE_DEBUG) && WARN_ABOUT_NON_PDF_OPERATIONS
#define notPossibleInPDFAssert jassertfalse
#else
#define notPossibleInPDFAssert
#endif
//==============================================================================
PDFWriter::PDFWriter (OutputStream &out_, const int dpi_, const bool colour_)
: dpi (dpi_),
colour (colour_),
needToClip (true),
clip(NULL),
currentGraphics(NULL),
page(NULL),
out(out_),
pdf(NULL)
{
}
PDFWriter::~PDFWriter()
{
delete clip;
delete currentGraphics;
if(pdf) HPDF_Free(pdf);
}
void PDFWriter::beginPrinting()
{
if (pdf)
{
jassertfalse // already printing
}
pdf = HPDF_New(NULL, NULL); //TODO: supply error handler
if (!pdf)
{
jassertfalse // could not create PDF handle
}
/* set compression mode */
HPDF_SetCompressionMode (pdf, HPDF_COMP_ALL);
/* set page mode to use outlines. */
HPDF_SetPageMode (pdf, HPDF_PAGE_MODE_USE_OUTLINE);
}
void PDFWriter::endPrinting()
{
if (!pdf)
{
jassertfalse // not printing
}
if (page)
{
jassertfalse // page not finished
}
//TODO: use the Juce OutputStream directly if possible
const int bufsize = 0x100;
HPDF_SaveToStream (pdf);
HPDF_ResetStream (pdf); /* rewind the stream. */
for (;;) /* copy the stream to out */
{
HPDF_BYTE buf[bufsize];
HPDF_UINT32 siz = bufsize;
HPDF_STATUS ret = HPDF_ReadFromStream (pdf, buf, &siz);
if (ret != HPDF_OK && ret != HPDF_STREAM_EOF || siz == 0)
break;
out.write(buf, siz);
}
HPDF_Free(pdf);
pdf = NULL;
}
Graphics *PDFWriter::beginPage(PageSize pageSize, Orientation orientation)
{
if (page)
{
jassertfalse // missing endPage()
}
page = HPDF_AddPage(pdf);
HPDF_Page_SetSize(page, (HPDF_PageSizes)pageSize, (HPDF_PageDirection)orientation);
HPDF_REAL width = HPDF_Page_GetWidth(page);
HPDF_REAL height = HPDF_Page_GetHeight(page);
clip = new RectangleList (Rectangle (0, 0, (int)width, (int)height));
HPDF_Page_Concat(page, 1, 0, 0, -1, 0, height);
return currentGraphics = new Graphics(this);
}
void PDFWriter::endPage()
{
if (!page)
{
jassertfalse // missing endPage()
}
page = NULL;
delete currentGraphics;
currentGraphics = NULL;
delete clip;
clip = NULL;
}
//==============================================================================
bool PDFWriter::isVectorDevice() const
{
return true;
}
void PDFWriter::setOrigin (int x, int y)
{
if (x != 0 || y != 0)
{
HPDF_Page_Concat(page, 1, 0, (HPDF_REAL)x, 0, 1, (HPDF_REAL)y);
needToClip = true;
}
}
bool PDFWriter::reduceClipRegion (int x, int y, int w, int h)
{
needToClip = true;
return clip->clipTo (Rectangle (x, y, w, h));
}
bool PDFWriter::reduceClipRegion (const RectangleList& clipRegion)
{
needToClip = true;
return clip->clipTo (clipRegion);
}
void PDFWriter::excludeClipRegion (int x, int y, int w, int h)
{
needToClip = true;
clip->subtract (Rectangle (x, y, w, h));
}
bool PDFWriter::clipRegionIntersects (int x, int y, int w, int h)
{
return clip->intersectsRectangle (Rectangle (x, y, w, h));
}
const Rectangle PDFWriter::getClipBounds() const
{
return clip->getBounds();
}
bool PDFWriter::isClipEmpty() const
{
return clip->isEmpty();
}
//==============================================================================
PDFWriter::SavedState::SavedState (RectangleList* const clip_,
const int xOffset_, const int yOffset_)
: clip (clip_),
xOffset (xOffset_),
yOffset (yOffset_)
{
}
PDFWriter::SavedState::~SavedState()
{
delete clip;
}
void PDFWriter::saveState()
{
stateStack.add (new SavedState (new RectangleList (*clip), 0, 0));
HPDF_Page_GSave(page);
}
void PDFWriter::restoreState()
{
SavedState* const top = stateStack.getLast();
if (top != 0)
{
clip->swapWith (*top->clip);
stateStack.removeLast();
needToClip = true;
HPDF_Page_GRestore(page);
}
else
{
jassertfalse // trying to pop with an empty stack!
}
}
//==============================================================================
void PDFWriter::pageClip()
{
// TODO: this should probably create a path of clipping rectangles
// and use the HPDF_Page_clip () function
if (needToClip)
{
needToClip = false;
for (RectangleList::Iterator i (*clip); i.next();)
{
const Rectangle& r = *i.getRectangle();
// out << r.getX() << ' ' << -r.getY() << ' '
// << r.getWidth() << ' ' << -r.getHeight() << " pr ";
}
// out << "endclip\n";
}
}
void PDFWriter::pageSetRGBFill (const Colour& colour)
{
Colour c (Colours::white.overlaidWith (colour));
HPDF_Page_SetRGBFill (page, c.getFloatRed(), c.getFloatGreen(), c.getFloatBlue());
}
void PDFWriter::pagePath (const Path& path)
{
Path::Iterator i (path);
while (i.next())
{
switch (i.elementType)
{
case Path::Iterator::startNewSubPath:
HPDF_Page_MoveTo(page, i.x1, i.y1);
break;
case Path::Iterator::lineTo:
HPDF_Page_LineTo(page, i.x1, i.y1);
break;
case Path::Iterator::quadraticTo:
HPDF_Page_CurveTo2(page, i.x1, i.y1, i.x2, i.y2);
break;
case Path::Iterator::cubicTo:
HPDF_Page_CurveTo(page, i.x1, i.y1, i.x2, i.y2, i.x3, i.y3);
break;
case Path::Iterator::closePath:
HPDF_Page_ClosePath(page);
break;
default:
jassertfalse
break;
}
}
}
void PDFWriter::fillRectWithColour (int x, int y, int w, int h, const Colour& colour, const bool /*replaceExistingContents*/)
{
pageClip ();
pageSetRGBFill (colour);
pageRectangle ((float)(x), (float)(y), (float)w, (float)h);
pageFill ();
}
void PDFWriter::fillRectWithGradient (int x, int y, int w, int h, const ColourGradient& gradient)
{
Path p;
p.addRectangle ((float) x, (float) y, (float) w, (float) h);
fillPathWithGradient (p, AffineTransform::identity, gradient, EdgeTable::Oversampling_256times);
}
//==============================================================================
void PDFWriter::fillPathWithColour (const Path& path, const AffineTransform& t,
const Colour& colour, EdgeTable::OversamplingLevel /*quality*/)
{
pageClip();
Path p (path);
p.applyTransform(t);
pageSetRGBFill (colour);
pagePath (p);
pageFill();
}
void PDFWriter::fillPathWithGradient (const Path& path, const AffineTransform& t, const ColourGradient& gradient, EdgeTable::OversamplingLevel /*quality*/)
{
pageClip ();
// this doesn't work correctly yet - it could be improved to handle solid gradients, but
// PDF can't do semi-transparent ones.
notPossibleInPDFAssert // you can disable this warning by setting the WARN_ABOUT_NON_PDF_OPERATIONS flag at the top of this file
int numColours = 256;
PixelARGB* const colours = gradient.createLookupTable (numColours);
for (int i = numColours; --i >= 0;)
colours[i].unpremultiply();
const Rectangle bounds (clip->getBounds());
pageSetRGBFill (Colour (colours [numColours / 2].getARGB()));
juce_free (colours);
pagePath (path);
pageFill ();
}
void PDFWriter::fillPathWithImage (const Path& path, const AffineTransform& transform,
const Image& sourceImage,
int imageX, int imageY,
float opacity, EdgeTable::OversamplingLevel /*quality*/)
{
pageClip ();
Path p (path);
p.applyTransform(transform);
pagePath (p);
blendImage (sourceImage, imageX, imageY, sourceImage.getWidth(), sourceImage.getHeight(), 0, 0, opacity);
}
//==============================================================================
void PDFWriter::fillAlphaChannelWithColour (const Image& /*clipImage*/, int x, int y, const Colour& colour)
{
pageClip();
notPossibleInPDFAssert // you can disable this warning by setting the WARN_ABOUT_NON_PDF_OPERATIONS flag at the top of this file
}
void PDFWriter::fillAlphaChannelWithGradient (const Image& /*alphaChannelImage*/, int imageX, int imageY, const ColourGradient& /*gradient*/)
{
pageClip();
notPossibleInPDFAssert // you can disable this warning by setting the WARN_ABOUT_NON_PDF_OPERATIONS flag at the top of this file
}
void PDFWriter::fillAlphaChannelWithImage (const Image& /*alphaImage*/, int alphaImageX, int alphaImageY,
const Image& /*fillerImage*/, int fillerImageX, int fillerImageY, float /*opacity*/)
{
pageClip();
notPossibleInPDFAssert // you can disable this warning by setting the WARN_ABOUT_NON_PDF_OPERATIONS flag at the top of this file
}
//==============================================================================
void PDFWriter::blendImageRescaling (const Image& sourceImage,
int dx, int dy, int dw, int dh,
int sx, int sy, int sw, int sh,
float alpha,
const Graphics::ResamplingQuality quality)
{
if (sw > 0 && sh > 0)
{
jassert (sx >= 0 && sx + sw <= sourceImage.getWidth());
jassert (sy >= 0 && sy + sh <= sourceImage.getHeight());
if (sw == dw && sh == dh)
{
blendImage (sourceImage,
dx, dy, dw, dh,
sx, sy, alpha);
}
else
{
blendImageWarping (sourceImage,
sx, sy, sw, sh,
AffineTransform::scale (dw / (float) sw,
dh / (float) sh)
.translated ((float) (dx - sx),
(float) (dy - sy)),
alpha,
quality);
}
}
}
//==============================================================================
void PDFWriter::blendImage (const Image& sourceImage, int dx, int dy, int dw, int dh, int sx, int sy, float opacity)
{
blendImageWarping (sourceImage,
sx, sy, dw, dh,
AffineTransform::translation ((float) dx, (float) dy),
opacity, Graphics::highResamplingQuality);
}
//==============================================================================
void PDFWriter::pageDrawImage (const Image& im, const int sx, const int sy, const int maxW, const int maxH) const
{
// out << "{<\n";
const int w = jmin (maxW, im.getWidth());
const int h = jmin (maxH, im.getHeight());
int charsOnLine = 0;
int lineStride, pixelStride;
const uint8* data = im.lockPixelDataReadOnly (0, 0, w, h, lineStride, pixelStride);
Colour pixel;
for (int y = h; --y >= 0;)
{
for (int x = 0; x < w; ++x)
{
const uint8* pixelData = data + lineStride * y + pixelStride * x;
if (x >= sx && y >= sy)
{
if (im.isARGB())
{
PixelARGB p (*(const PixelARGB*) pixelData);
p.unpremultiply();
pixel = Colours::white.overlaidWith (Colour (p.getARGB()));
}
else if (im.isRGB())
{
pixel = Colour (((const PixelRGB*) pixelData)->getARGB());
}
else
{
pixel = Colour ((uint8) 0, (uint8) 0, (uint8) 0, *pixelData);
}
}
else
{
pixel = Colours::transparentWhite;
}
char colourString [16];
sprintf (colourString, "%x%x%x", pixel.getRed(), pixel.getGreen(), pixel.getBlue());
// out << (const char*) colourString;
charsOnLine += 3;
if (charsOnLine > 100)
{
// out << '\n';
charsOnLine = 0;
}
}
}
im.releasePixelDataReadOnly (data);
// out << "\n>}\n";
}
void PDFWriter::blendImageWarping (const Image& sourceImage,
int srcClipX, int srcClipY,
int srcClipW, int srcClipH,
const AffineTransform& t,
float /*opacity*/,
const Graphics::ResamplingQuality /*quality*/)
{
const int w = jmin (sourceImage.getWidth(), srcClipX + srcClipW);
const int h = jmin (sourceImage.getHeight(), srcClipY + srcClipH);
pageClip();
RectangleList imageClip;
sourceImage.createSolidAreaMask (imageClip, 0.5f);
imageClip.clipTo (Rectangle (srcClipX, srcClipY, srcClipW, srcClipH));
for (RectangleList::Iterator i (imageClip); i.next();)
{
const Rectangle& r = *i.getRectangle();
// out << r.getX() << ' ' << r.getY() << ' ' << r.getWidth() << ' ' << r.getHeight() << " pr ";
}
// out << " clip newpath\n";
// out << w << ' ' << h << " scale\n";
// out << w << ' ' << h << " 8 [" << w << " 0 0 -" << h << ' ' << (int) 0 << ' ' << h << " ]\n";
pageDrawImage (sourceImage, srcClipX, srcClipY, srcClipW, srcClipH);
// out << "false 3 colorimage grestore\n";
needToClip = true;
}
//==============================================================================
void PDFWriter::drawLine (double x1, double y1, double x2, double y2, const Colour& colour)
{
Path p;
p.addLineSegment ((float) x1, (float) y1, (float) x2, (float) y2, 1.0f);
fillPathWithColour (p, AffineTransform::identity, colour, EdgeTable::Oversampling_256times);
}
void PDFWriter::drawVerticalLine (const int x, double top, double bottom, const Colour& col)
{
drawLine (x, top, x, bottom, col);
}
void PDFWriter::drawHorizontalLine (const int y, double left, double right, const Colour& col)
{
drawLine (left, y, right, y, col);
}
void PDFWriter::pageRectangle(float x, float y, float w, float h)
{
Path p;
p.startNewSubPath (x, y);
p.lineTo(x + w, y);
p.lineTo(x + w, y + h);
p.lineTo(x, y + h);
p.closeSubPath();
pagePath(p);
}
void PDFWriter::pageFill()
{
HPDF_Page_Fill(page);
}
END_JUCE_NAMESPACE
I don’t have a C++ sample of how to use it, I tested it from PILS.