summaryrefslogtreecommitdiffstats
path: root/vcl/unx/generic/print
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/unx/generic/print
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--vcl/unx/generic/print/GenPspGfxBackend.cxx412
-rw-r--r--vcl/unx/generic/print/bitmap_gfx.cxx674
-rw-r--r--vcl/unx/generic/print/common_gfx.cxx1152
-rw-r--r--vcl/unx/generic/print/genprnpsp.cxx1298
-rw-r--r--vcl/unx/generic/print/genpspgraphics.cxx521
-rw-r--r--vcl/unx/generic/print/glyphset.cxx301
-rw-r--r--vcl/unx/generic/print/glyphset.hxx81
-rw-r--r--vcl/unx/generic/print/printerjob.cxx973
-rw-r--r--vcl/unx/generic/print/prtsetup.cxx516
-rw-r--r--vcl/unx/generic/print/prtsetup.hxx138
-rw-r--r--vcl/unx/generic/print/psheader.ps363
-rw-r--r--vcl/unx/generic/print/psputil.cxx184
-rw-r--r--vcl/unx/generic/print/psputil.hxx55
-rw-r--r--vcl/unx/generic/print/text_gfx.cxx158
-rw-r--r--vcl/unx/generic/printer/configuration/README5
-rw-r--r--vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS582
-rw-r--r--vcl/unx/generic/printer/configuration/psprint.conf99
-rw-r--r--vcl/unx/generic/printer/cpdmgr.cxx757
-rw-r--r--vcl/unx/generic/printer/cupsmgr.cxx964
-rw-r--r--vcl/unx/generic/printer/jobdata.cxx316
-rw-r--r--vcl/unx/generic/printer/ppdparser.cxx1991
-rw-r--r--vcl/unx/generic/printer/printerinfomanager.cxx892
22 files changed, 12432 insertions, 0 deletions
diff --git a/vcl/unx/generic/print/GenPspGfxBackend.cxx b/vcl/unx/generic/print/GenPspGfxBackend.cxx
new file mode 100644
index 000000000..7b461ff4f
--- /dev/null
+++ b/vcl/unx/generic/print/GenPspGfxBackend.cxx
@@ -0,0 +1,412 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <unx/GenPspGfxBackend.hxx>
+#include <unx/printergfx.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <salbmp.hxx>
+
+// ----- Implementation of PrinterBmp by means of SalBitmap/BitmapBuffer ---------------
+
+namespace
+{
+class SalPrinterBmp : public psp::PrinterBmp
+{
+private:
+ BitmapBuffer* mpBmpBuffer;
+
+ FncGetPixel mpFncGetPixel;
+ Scanline mpScanAccess;
+ sal_PtrDiff mnScanOffset;
+
+public:
+ explicit SalPrinterBmp(BitmapBuffer* pBitmap);
+
+ virtual sal_uInt32 GetPaletteColor(sal_uInt32 nIdx) const override;
+ virtual sal_uInt32 GetPaletteEntryCount() const override;
+ virtual sal_uInt32 GetPixelRGB(sal_uInt32 nRow, sal_uInt32 nColumn) const override;
+ virtual sal_uInt8 GetPixelGray(sal_uInt32 nRow, sal_uInt32 nColumn) const override;
+ virtual sal_uInt8 GetPixelIdx(sal_uInt32 nRow, sal_uInt32 nColumn) const override;
+ virtual sal_uInt32 GetDepth() const override;
+};
+}
+
+SalPrinterBmp::SalPrinterBmp(BitmapBuffer* pBuffer)
+ : mpBmpBuffer(pBuffer)
+{
+ assert(mpBmpBuffer && "SalPrinterBmp::SalPrinterBmp () can't acquire Bitmap");
+
+ // calibrate scanline buffer
+ if (mpBmpBuffer->mnFormat & ScanlineFormat::TopDown)
+ {
+ mpScanAccess = mpBmpBuffer->mpBits;
+ mnScanOffset = mpBmpBuffer->mnScanlineSize;
+ }
+ else
+ {
+ mpScanAccess
+ = mpBmpBuffer->mpBits + (mpBmpBuffer->mnHeight - 1) * mpBmpBuffer->mnScanlineSize;
+ mnScanOffset = -mpBmpBuffer->mnScanlineSize;
+ }
+
+ // request read access to the pixels
+ mpFncGetPixel = BitmapReadAccess::GetPixelFunction(mpBmpBuffer->mnFormat);
+}
+
+sal_uInt32 SalPrinterBmp::GetDepth() const
+{
+ sal_uInt32 nDepth;
+
+ switch (mpBmpBuffer->mnBitCount)
+ {
+ case 1:
+ nDepth = 1;
+ break;
+
+ case 4:
+ case 8:
+ nDepth = 8;
+ break;
+
+ case 24:
+ case 32:
+ nDepth = 24;
+ break;
+
+ default:
+ nDepth = 1;
+ assert(false && "Error: unsupported bitmap depth in SalPrinterBmp::GetDepth()");
+ break;
+ }
+
+ return nDepth;
+}
+
+sal_uInt32 SalPrinterBmp::GetPaletteEntryCount() const
+{
+ return mpBmpBuffer->maPalette.GetEntryCount();
+}
+
+sal_uInt32 SalPrinterBmp::GetPaletteColor(sal_uInt32 nIdx) const
+{
+ BitmapColor aColor(mpBmpBuffer->maPalette[nIdx]);
+
+ return ((aColor.GetBlue()) & 0x000000ff) | ((aColor.GetGreen() << 8) & 0x0000ff00)
+ | ((aColor.GetRed() << 16) & 0x00ff0000);
+}
+
+sal_uInt32 SalPrinterBmp::GetPixelRGB(sal_uInt32 nRow, sal_uInt32 nColumn) const
+{
+ Scanline pScan = mpScanAccess + nRow * mnScanOffset;
+ BitmapColor aColor = mpFncGetPixel(pScan, nColumn, mpBmpBuffer->maColorMask);
+
+ if (!!mpBmpBuffer->maPalette)
+ GetPaletteColor(aColor.GetIndex());
+
+ return ((aColor.GetBlue()) & 0x000000ff) | ((aColor.GetGreen() << 8) & 0x0000ff00)
+ | ((aColor.GetRed() << 16) & 0x00ff0000);
+}
+
+sal_uInt8 SalPrinterBmp::GetPixelGray(sal_uInt32 nRow, sal_uInt32 nColumn) const
+{
+ Scanline pScan = mpScanAccess + nRow * mnScanOffset;
+ BitmapColor aColor = mpFncGetPixel(pScan, nColumn, mpBmpBuffer->maColorMask);
+
+ if (!!mpBmpBuffer->maPalette)
+ aColor = mpBmpBuffer->maPalette[aColor.GetIndex()];
+
+ return (aColor.GetBlue() * 28UL + aColor.GetGreen() * 151UL + aColor.GetRed() * 77UL) >> 8;
+}
+
+sal_uInt8 SalPrinterBmp::GetPixelIdx(sal_uInt32 nRow, sal_uInt32 nColumn) const
+{
+ Scanline pScan = mpScanAccess + nRow * mnScanOffset;
+ BitmapColor aColor = mpFncGetPixel(pScan, nColumn, mpBmpBuffer->maColorMask);
+
+ if (!!mpBmpBuffer->maPalette)
+ return aColor.GetIndex();
+ else
+ return 0;
+}
+
+GenPspGfxBackend::GenPspGfxBackend(psp::PrinterGfx* pPrinterGfx)
+ : m_pPrinterGfx(pPrinterGfx)
+{
+}
+
+GenPspGfxBackend::~GenPspGfxBackend() {}
+
+void GenPspGfxBackend::Init() {}
+void GenPspGfxBackend::freeResources() {}
+
+bool GenPspGfxBackend::setClipRegion(vcl::Region const& rRegion)
+{
+ // TODO: support polygonal clipregions here
+ RectangleVector aRectangles;
+ rRegion.GetRegionRectangles(aRectangles);
+ m_pPrinterGfx->BeginSetClipRegion();
+
+ for (auto const& rectangle : aRectangles)
+ {
+ const tools::Long nWidth(rectangle.GetWidth());
+ const tools::Long nHeight(rectangle.GetHeight());
+
+ if (nWidth && nHeight)
+ {
+ m_pPrinterGfx->UnionClipRegion(rectangle.Left(), rectangle.Top(), nWidth, nHeight);
+ }
+ }
+
+ m_pPrinterGfx->EndSetClipRegion();
+
+ return true;
+}
+
+void GenPspGfxBackend::ResetClipRegion() { m_pPrinterGfx->ResetClipRegion(); }
+
+sal_uInt16 GenPspGfxBackend::GetBitCount() const { return m_pPrinterGfx->GetBitCount(); }
+
+tools::Long GenPspGfxBackend::GetGraphicsWidth() const { return 0; }
+
+void GenPspGfxBackend::SetLineColor() { m_pPrinterGfx->SetLineColor(); }
+
+void GenPspGfxBackend::SetLineColor(Color nColor)
+{
+ psp::PrinterColor aColor(nColor.GetRed(), nColor.GetGreen(), nColor.GetBlue());
+ m_pPrinterGfx->SetLineColor(aColor);
+}
+
+void GenPspGfxBackend::SetFillColor() { m_pPrinterGfx->SetFillColor(); }
+
+void GenPspGfxBackend::SetFillColor(Color nColor)
+{
+ psp::PrinterColor aColor(nColor.GetRed(), nColor.GetGreen(), nColor.GetBlue());
+ m_pPrinterGfx->SetFillColor(aColor);
+}
+
+void GenPspGfxBackend::SetXORMode(bool bSet, bool /*bInvertOnly*/)
+{
+ SAL_WARN_IF(bSet, "vcl", "Error: PrinterGfx::SetXORMode() not implemented");
+}
+
+void GenPspGfxBackend::SetROPLineColor(SalROPColor /*nROPColor*/)
+{
+ SAL_WARN("vcl", "Error: PrinterGfx::SetROPLineColor() not implemented");
+}
+
+void GenPspGfxBackend::SetROPFillColor(SalROPColor /*nROPColor*/)
+{
+ SAL_WARN("vcl", "Error: PrinterGfx::SetROPFillColor() not implemented");
+}
+
+void GenPspGfxBackend::drawPixel(tools::Long nX, tools::Long nY)
+{
+ m_pPrinterGfx->DrawPixel(Point(nX, nY));
+}
+void GenPspGfxBackend::drawPixel(tools::Long nX, tools::Long nY, Color nColor)
+{
+ psp::PrinterColor aColor(nColor.GetRed(), nColor.GetGreen(), nColor.GetBlue());
+ m_pPrinterGfx->DrawPixel(Point(nX, nY), aColor);
+}
+
+void GenPspGfxBackend::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2)
+{
+ m_pPrinterGfx->DrawLine(Point(nX1, nY1), Point(nX2, nY2));
+}
+void GenPspGfxBackend::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight)
+{
+ m_pPrinterGfx->DrawRect(tools::Rectangle(Point(nX, nY), Size(nWidth, nHeight)));
+}
+
+void GenPspGfxBackend::drawPolyLine(sal_uInt32 nPoints, const Point* pPointArray)
+{
+ m_pPrinterGfx->DrawPolyLine(nPoints, pPointArray);
+}
+
+void GenPspGfxBackend::drawPolygon(sal_uInt32 nPoints, const Point* pPointArray)
+{
+ // Point must be equal to Point! see include/vcl/salgtype.hxx
+ m_pPrinterGfx->DrawPolygon(nPoints, pPointArray);
+}
+
+void GenPspGfxBackend::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point** pPointArray)
+{
+ m_pPrinterGfx->DrawPolyPolygon(nPoly, pPoints, pPointArray);
+}
+
+bool GenPspGfxBackend::drawPolyPolygon(const basegfx::B2DHomMatrix& /*rObjectToDevice*/,
+ const basegfx::B2DPolyPolygon&, double /*fTransparency*/)
+{
+ // TODO: implement and advertise OutDevSupportType::B2DDraw support
+ return false;
+}
+
+bool GenPspGfxBackend::drawPolyLine(const basegfx::B2DHomMatrix& /*rObjectToDevice*/,
+ const basegfx::B2DPolygon& /*rPolygon*/,
+ double /*fTransparency*/, double /*fLineWidth*/,
+ const std::vector<double>* /*pStroke*/, basegfx::B2DLineJoin,
+ css::drawing::LineCap, double /*fMiterMinimumAngle*/,
+ bool /*bPixelSnapHairline*/)
+{
+ // TODO: a PS printer can draw B2DPolyLines almost directly
+ return false;
+}
+
+bool GenPspGfxBackend::drawPolyLineBezier(sal_uInt32 nPoints, const Point* pPointArray,
+ const PolyFlags* pFlagArray)
+{
+ m_pPrinterGfx->DrawPolyLineBezier(nPoints, pPointArray, pFlagArray);
+ return true;
+}
+
+bool GenPspGfxBackend::drawPolygonBezier(sal_uInt32 nPoints, const Point* pPointArray,
+ const PolyFlags* pFlagArray)
+{
+ m_pPrinterGfx->DrawPolygonBezier(nPoints, pPointArray, pFlagArray);
+ return true;
+}
+
+bool GenPspGfxBackend::drawPolyPolygonBezier(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point* const* pPointArray,
+ const PolyFlags* const* pFlagArray)
+{
+ // Point must be equal to Point! see include/vcl/salgtype.hxx
+ m_pPrinterGfx->DrawPolyPolygonBezier(nPoly, pPoints, pPointArray, pFlagArray);
+ return true;
+}
+
+void GenPspGfxBackend::copyArea(tools::Long /*nDestX*/, tools::Long /*nDestY*/,
+ tools::Long /*nSrcX*/, tools::Long /*nSrcY*/,
+ tools::Long /*nSrcWidth*/, tools::Long /*nSrcHeight*/,
+ bool /*bWindowInvalidate*/)
+{
+ OSL_FAIL("Error: PrinterGfx::CopyArea() not implemented");
+}
+
+void GenPspGfxBackend::copyBits(const SalTwoRect& /*rPosAry*/, SalGraphics* /*pSrcGraphics*/)
+{
+ OSL_FAIL("Error: PrinterGfx::CopyBits() not implemented");
+}
+
+void GenPspGfxBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
+{
+ tools::Rectangle aSrc(Point(rPosAry.mnSrcX, rPosAry.mnSrcY),
+ Size(rPosAry.mnSrcWidth, rPosAry.mnSrcHeight));
+
+ tools::Rectangle aDst(Point(rPosAry.mnDestX, rPosAry.mnDestY),
+ Size(rPosAry.mnDestWidth, rPosAry.mnDestHeight));
+
+ BitmapBuffer* pBuffer
+ = const_cast<SalBitmap&>(rSalBitmap).AcquireBuffer(BitmapAccessMode::Read);
+
+ SalPrinterBmp aBmp(pBuffer);
+ m_pPrinterGfx->DrawBitmap(aDst, aSrc, aBmp);
+
+ const_cast<SalBitmap&>(rSalBitmap).ReleaseBuffer(pBuffer, BitmapAccessMode::Read);
+}
+
+void GenPspGfxBackend::drawBitmap(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rSalBitmap*/,
+ const SalBitmap& /*rMaskBitmap*/)
+{
+ OSL_FAIL("Error: no PrinterGfx::DrawBitmap() for transparent bitmap");
+}
+
+void GenPspGfxBackend::drawMask(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rSalBitmap*/,
+ Color /*nMaskColor*/)
+{
+ OSL_FAIL("Error: PrinterGfx::DrawMask() not implemented");
+}
+
+std::shared_ptr<SalBitmap> GenPspGfxBackend::getBitmap(tools::Long /*nX*/, tools::Long /*nY*/,
+ tools::Long /*nWidth*/,
+ tools::Long /*nHeight*/)
+{
+ SAL_INFO("vcl", "Warning: PrinterGfx::GetBitmap() not implemented");
+ return nullptr;
+}
+
+Color GenPspGfxBackend::getPixel(tools::Long /*nX*/, tools::Long /*nY*/)
+{
+ OSL_FAIL("Warning: PrinterGfx::GetPixel() not implemented");
+ return Color();
+}
+
+void GenPspGfxBackend::invert(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/,
+ tools::Long /*nHeight*/, SalInvert /*nFlags*/)
+{
+ OSL_FAIL("Warning: PrinterGfx::Invert() not implemented");
+}
+
+void GenPspGfxBackend::invert(sal_uInt32 /*nPoints*/, const Point* /*pPtAry*/, SalInvert /*nFlags*/)
+{
+ SAL_WARN("vcl", "Error: PrinterGfx::Invert() not implemented");
+}
+
+bool GenPspGfxBackend::drawEPS(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, void* pPtr, sal_uInt32 nSize)
+{
+ return m_pPrinterGfx->DrawEPS(tools::Rectangle(Point(nX, nY), Size(nWidth, nHeight)), pPtr,
+ nSize);
+}
+
+bool GenPspGfxBackend::blendBitmap(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rBitmap*/)
+{
+ return false;
+}
+
+bool GenPspGfxBackend::blendAlphaBitmap(const SalTwoRect& /*rPosAry*/,
+ const SalBitmap& /*rSrcBitmap*/,
+ const SalBitmap& /*rMaskBitmap*/,
+ const SalBitmap& /*rAlphaBitmap*/)
+{
+ return false;
+}
+
+bool GenPspGfxBackend::drawAlphaBitmap(const SalTwoRect& /*rPosAry*/,
+ const SalBitmap& /*rSourceBitmap*/,
+ const SalBitmap& /*rAlphaBitmap*/)
+{
+ return false;
+}
+
+bool GenPspGfxBackend::drawTransformedBitmap(const basegfx::B2DPoint& /*rNull*/,
+ const basegfx::B2DPoint& /*rX*/,
+ const basegfx::B2DPoint& /*rY*/,
+ const SalBitmap& /*rSourceBitmap*/,
+ const SalBitmap* /*pAlphaBitmap*/, double /*fAlpha*/)
+{
+ return false;
+}
+
+bool GenPspGfxBackend::hasFastDrawTransformedBitmap() const { return false; }
+
+bool GenPspGfxBackend::drawAlphaRect(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/,
+ tools::Long /*nHeight*/, sal_uInt8 /*nTransparency*/)
+{
+ return false;
+}
+
+bool GenPspGfxBackend::drawGradient(const tools::PolyPolygon& /*rPolygon*/,
+ const Gradient& /*rGradient*/)
+{
+ return false;
+}
+
+bool GenPspGfxBackend::implDrawGradient(basegfx::B2DPolyPolygon const& /*rPolyPolygon*/,
+ SalGradient const& /*rGradient*/)
+{
+ return false;
+}
+
+bool GenPspGfxBackend::supportsOperation(OutDevSupportType /*eType*/) const { return false; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/bitmap_gfx.cxx b/vcl/unx/generic/print/bitmap_gfx.cxx
new file mode 100644
index 000000000..2d8649706
--- /dev/null
+++ b/vcl/unx/generic/print/bitmap_gfx.cxx
@@ -0,0 +1,674 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <array>
+#include <memory>
+#include "psputil.hxx"
+
+#include <unx/printergfx.hxx>
+
+namespace psp {
+
+const sal_uInt32 nLineLength = 80;
+const sal_uInt32 nBufferSize = 16384;
+
+/*
+ *
+ * Bitmap compression / Hex encoding / Ascii85 Encoding
+ *
+ */
+
+PrinterBmp::~PrinterBmp()
+{
+}
+
+/* HexEncoder */
+
+namespace {
+
+class HexEncoder
+{
+private:
+
+ osl::File* mpFile;
+ sal_uInt32 mnColumn;
+ sal_uInt32 mnOffset;
+ OStringBuffer mpFileBuffer;
+
+public:
+
+ explicit HexEncoder (osl::File* pFile);
+ ~HexEncoder ();
+ void WriteAscii (sal_uInt8 nByte);
+ void EncodeByte (sal_uInt8 nByte);
+ void FlushLine ();
+};
+
+}
+
+HexEncoder::HexEncoder (osl::File* pFile) :
+ mpFile (pFile),
+ mnColumn (0),
+ mnOffset (0)
+{}
+
+HexEncoder::~HexEncoder ()
+{
+ FlushLine ();
+ if (mnColumn > 0)
+ WritePS (mpFile, "\n");
+}
+
+void
+HexEncoder::WriteAscii (sal_uInt8 nByte)
+{
+ sal_uInt32 nOff = psp::getHexValueOf (nByte, mpFileBuffer);
+ mnColumn += nOff;
+ mnOffset += nOff;
+
+ if (mnColumn >= nLineLength)
+ {
+ mnOffset += psp::appendStr ("\n", mpFileBuffer);
+ mnColumn = 0;
+ }
+ if (mnOffset >= nBufferSize)
+ FlushLine ();
+}
+
+void
+HexEncoder::EncodeByte (sal_uInt8 nByte)
+{
+ WriteAscii (nByte);
+}
+
+void
+HexEncoder::FlushLine ()
+{
+ if (mnOffset > 0)
+ {
+ WritePS (mpFile, mpFileBuffer.makeStringAndClear());
+ mnOffset = 0;
+ }
+}
+
+/* Ascii85 encoder, is abi compatible with HexEncoder but writes a ~> to
+ indicate end of data EOD */
+
+namespace {
+
+class Ascii85Encoder
+{
+private:
+
+ osl::File* mpFile;
+ sal_uInt32 mnByte;
+ sal_uInt8 mpByteBuffer[4];
+
+ sal_uInt32 mnColumn;
+ sal_uInt32 mnOffset;
+ OStringBuffer mpFileBuffer;
+
+ inline void PutByte (sal_uInt8 nByte);
+ inline void PutEOD ();
+ void ConvertToAscii85 ();
+ void FlushLine ();
+
+public:
+
+ explicit Ascii85Encoder (osl::File* pFile);
+ virtual ~Ascii85Encoder ();
+ virtual void EncodeByte (sal_uInt8 nByte);
+ void WriteAscii (sal_uInt8 nByte);
+};
+
+}
+
+Ascii85Encoder::Ascii85Encoder (osl::File* pFile) :
+ mpFile (pFile),
+ mnByte (0),
+ mnColumn (0),
+ mnOffset (0)
+{}
+
+inline void
+Ascii85Encoder::PutByte (sal_uInt8 nByte)
+{
+ mpByteBuffer [mnByte++] = nByte;
+}
+
+inline void
+Ascii85Encoder::PutEOD ()
+{
+ WritePS (mpFile, "~>\n");
+}
+
+void
+Ascii85Encoder::ConvertToAscii85 ()
+{
+ // Add (4 - mnByte) zero padding bytes:
+ if (mnByte < 4)
+ std::memset (mpByteBuffer + mnByte, 0, (4 - mnByte) * sizeof(sal_uInt8));
+
+ sal_uInt32 nByteValue = mpByteBuffer[0] * 256 * 256 * 256
+ + mpByteBuffer[1] * 256 * 256
+ + mpByteBuffer[2] * 256
+ + mpByteBuffer[3];
+
+ if (nByteValue == 0 && mnByte == 4)
+ {
+ /* special case of 4 Bytes in row */
+ mpFileBuffer.append('z');
+
+ mnOffset += 1;
+ mnColumn += 1;
+ }
+ else
+ {
+ /* real ascii85 encoding */
+
+ // Of the up to 5 characters to be generated, do not generate the last (4 - mnByte) ones
+ // that correspond to the (4 - mnByte) zero padding bytes added to the input:
+
+ auto const pos = mpFileBuffer.getLength();
+ if (mnByte == 4) {
+ mpFileBuffer.insert(pos, char((nByteValue % 85) + 33));
+ }
+ nByteValue /= 85;
+ if (mnByte >= 3) {
+ mpFileBuffer.insert(pos, char((nByteValue % 85) + 33));
+ }
+ nByteValue /= 85;
+ if (mnByte >= 2) {
+ mpFileBuffer.insert(pos, char((nByteValue % 85) + 33));
+ }
+ nByteValue /= 85;
+ mpFileBuffer.insert(pos, char((nByteValue % 85) + 33));
+ nByteValue /= 85;
+ mpFileBuffer.insert(pos, char((nByteValue % 85) + 33));
+
+ mnColumn += (mnByte + 1);
+ mnOffset += (mnByte + 1);
+
+ /* insert a newline if necessary */
+ if (mnColumn > nLineLength)
+ {
+ sal_uInt32 nEolOff = mnColumn - nLineLength;
+ auto const posNl = pos + (mnByte + 1) - nEolOff;
+
+ mpFileBuffer.insert(posNl, '\n');
+
+ mnOffset++;
+ mnColumn = nEolOff;
+ }
+ }
+
+ mnByte = 0;
+}
+
+void
+Ascii85Encoder::WriteAscii (sal_uInt8 nByte)
+{
+ PutByte (nByte);
+ if (mnByte == 4)
+ ConvertToAscii85 ();
+
+ if (mnColumn >= nLineLength)
+ {
+ mnOffset += psp::appendStr ("\n", mpFileBuffer);
+ mnColumn = 0;
+ }
+ if (mnOffset >= nBufferSize)
+ FlushLine ();
+}
+
+void
+Ascii85Encoder::EncodeByte (sal_uInt8 nByte)
+{
+ WriteAscii (nByte);
+}
+
+void
+Ascii85Encoder::FlushLine ()
+{
+ if (mnOffset > 0)
+ {
+ WritePS (mpFile, mpFileBuffer.makeStringAndClear());
+ mnOffset = 0;
+ }
+}
+
+Ascii85Encoder::~Ascii85Encoder ()
+{
+ if (mnByte > 0)
+ ConvertToAscii85 ();
+ if (mnOffset > 0)
+ FlushLine ();
+ PutEOD ();
+}
+
+/* LZW encoder */
+
+namespace {
+
+class LZWEncoder : public Ascii85Encoder
+{
+private:
+
+ struct LZWCTreeNode
+ {
+ LZWCTreeNode* mpBrother; // next node with same parent
+ LZWCTreeNode* mpFirstChild; // first son
+ sal_uInt16 mnCode; // code for the string
+ sal_uInt16 mnValue; // pixelvalue
+ };
+
+ std::array<LZWCTreeNode, 4096>
+ maTable; // LZW compression data
+ LZWCTreeNode* mpPrefix; // the compression is as same as the TIFF compression
+ static constexpr sal_uInt16 gnDataSize = 8;
+ static constexpr sal_uInt16 gnClearCode = 1 << gnDataSize;
+ static constexpr sal_uInt16 gnEOICode = gnClearCode + 1;
+ sal_uInt16 mnTableSize;
+ sal_uInt16 mnCodeSize;
+ sal_uInt32 mnOffset;
+ sal_uInt32 mdwShift;
+
+ void WriteBits (sal_uInt16 nCode, sal_uInt16 nCodeLen);
+
+public:
+
+ explicit LZWEncoder (osl::File* pOutputFile);
+ virtual ~LZWEncoder () override;
+
+ virtual void EncodeByte (sal_uInt8 nByte) override;
+};
+
+}
+
+LZWEncoder::LZWEncoder(osl::File* pOutputFile) :
+ Ascii85Encoder (pOutputFile),
+ maTable{{}},
+ mpPrefix(nullptr),
+ mnTableSize(gnEOICode + 1),
+ mnCodeSize(gnDataSize + 1),
+ mnOffset(32), // free bits in dwShift
+ mdwShift(0)
+{
+ for (sal_uInt32 i = 0; i < 4096; i++)
+ {
+ maTable[i].mpBrother = nullptr;
+ maTable[i].mpFirstChild = nullptr;
+ maTable[i].mnCode = i;
+ maTable[i].mnValue = static_cast<sal_uInt8>(maTable[i].mnCode);
+ }
+
+ WriteBits( gnClearCode, mnCodeSize );
+}
+
+LZWEncoder::~LZWEncoder()
+{
+ if (mpPrefix)
+ WriteBits (mpPrefix->mnCode, mnCodeSize);
+
+ WriteBits (gnEOICode, mnCodeSize);
+}
+
+void
+LZWEncoder::WriteBits (sal_uInt16 nCode, sal_uInt16 nCodeLen)
+{
+ mdwShift |= (nCode << (mnOffset - nCodeLen));
+ mnOffset -= nCodeLen;
+ while (mnOffset < 24)
+ {
+ WriteAscii (static_cast<sal_uInt8>(mdwShift >> 24));
+ mdwShift <<= 8;
+ mnOffset += 8;
+ }
+ if (nCode == 257 && mnOffset != 32)
+ WriteAscii (static_cast<sal_uInt8>(mdwShift >> 24));
+}
+
+void
+LZWEncoder::EncodeByte (sal_uInt8 nByte )
+{
+ LZWCTreeNode* p;
+ sal_uInt16 i;
+ sal_uInt8 nV;
+
+ if (!mpPrefix)
+ {
+ mpPrefix = maTable.data() + nByte;
+ }
+ else
+ {
+ nV = nByte;
+ for (p = mpPrefix->mpFirstChild; p != nullptr; p = p->mpBrother)
+ {
+ if (p->mnValue == nV)
+ break;
+ }
+
+ if (p != nullptr)
+ {
+ mpPrefix = p;
+ }
+ else
+ {
+ WriteBits (mpPrefix->mnCode, mnCodeSize);
+
+ if (mnTableSize == 409)
+ {
+ WriteBits (gnClearCode, mnCodeSize);
+
+ for (i = 0; i < gnClearCode; i++)
+ maTable[i].mpFirstChild = nullptr;
+
+ mnCodeSize = gnDataSize + 1;
+ mnTableSize = gnEOICode + 1;
+ }
+ else
+ {
+ if(mnTableSize == static_cast<sal_uInt16>((1 << mnCodeSize) - 1))
+ mnCodeSize++;
+
+ p = maTable.data() + (mnTableSize++);
+ p->mpBrother = mpPrefix->mpFirstChild;
+ mpPrefix->mpFirstChild = p;
+ p->mnValue = nV;
+ p->mpFirstChild = nullptr;
+ }
+
+ mpPrefix = maTable.data() + nV;
+ }
+ }
+}
+
+/*
+ *
+ * bitmap handling routines
+ *
+ */
+
+void
+PrinterGfx::DrawBitmap (const tools::Rectangle& rDest, const tools::Rectangle& rSrc,
+ const PrinterBmp& rBitmap)
+{
+ double fScaleX = static_cast<double>(rDest.GetWidth());
+ double fScaleY = static_cast<double>(rDest.GetHeight());
+ if(rSrc.GetWidth() > 0)
+ {
+ fScaleX = static_cast<double>(rDest.GetWidth()) / static_cast<double>(rSrc.GetWidth());
+ }
+ if(rSrc.GetHeight() > 0)
+ {
+ fScaleY = static_cast<double>(rDest.GetHeight()) / static_cast<double>(rSrc.GetHeight());
+ }
+ PSGSave ();
+ PSTranslate (rDest.BottomLeft());
+ PSScale (fScaleX, fScaleY);
+
+ if (mnPSLevel >= 2)
+ {
+ if (rBitmap.GetDepth() == 1)
+ {
+ DrawPS2MonoImage (rBitmap, rSrc);
+ }
+ else
+ if (rBitmap.GetDepth() == 8 && mbColor)
+ {
+ // if the palette is larger than the image itself print it as a truecolor
+ // image to save diskspace. This is important for printing transparent
+ // bitmaps that are disassembled into small pieces
+ sal_Int32 nImageSz = rSrc.GetWidth() * rSrc.GetHeight();
+ sal_Int32 nPaletteSz = rBitmap.GetPaletteEntryCount();
+ if ((nImageSz < nPaletteSz) || (nImageSz < 24) )
+ DrawPS2TrueColorImage (rBitmap, rSrc);
+ else
+ DrawPS2PaletteImage (rBitmap, rSrc);
+ }
+ else
+ if (rBitmap.GetDepth() == 24 && mbColor)
+ {
+ DrawPS2TrueColorImage (rBitmap, rSrc);
+ }
+ else
+ {
+ DrawPS2GrayImage (rBitmap, rSrc);
+ }
+ }
+ else
+ {
+ DrawPS1GrayImage (rBitmap, rSrc);
+ }
+
+ PSGRestore ();
+}
+
+/*
+ *
+ * Implementation: PS Level 1
+ *
+ */
+
+void
+PrinterGfx::DrawPS1GrayImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea)
+{
+ sal_uInt32 nWidth = rArea.GetWidth();
+ sal_uInt32 nHeight = rArea.GetHeight();
+
+ OStringBuffer pGrayImage;
+
+ // image header
+ psp::getValueOf (nWidth, pGrayImage);
+ psp::appendStr (" ", pGrayImage);
+ psp::getValueOf (nHeight, pGrayImage);
+ psp::appendStr (" 8 ", pGrayImage);
+ psp::appendStr ("[ 1 0 0 1 0 ", pGrayImage);
+ psp::getValueOf (nHeight, pGrayImage);
+ psp::appendStr ("]", pGrayImage);
+ psp::appendStr (" {currentfile ", pGrayImage);
+ psp::getValueOf (nWidth, pGrayImage);
+ psp::appendStr (" string readhexstring pop}\n", pGrayImage);
+ psp::appendStr ("image\n", pGrayImage);
+
+ WritePS (mpPageBody, pGrayImage.makeStringAndClear());
+
+ // image body
+ HexEncoder aEncoder(mpPageBody);
+
+ for (tools::Long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
+ {
+ for (tools::Long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
+ {
+ unsigned char nByte = rBitmap.GetPixelGray (nRow, nColumn);
+ aEncoder.EncodeByte (nByte);
+ }
+ }
+
+ WritePS (mpPageBody, "\n");
+}
+
+/*
+ *
+ * Implementation: PS Level 2
+ *
+ */
+
+void
+PrinterGfx::writePS2ImageHeader (const tools::Rectangle& rArea, psp::ImageType nType)
+{
+ OStringBuffer pImage;
+
+ sal_Int32 nDictType = 0;
+ switch (nType)
+ {
+ case psp::ImageType::TrueColorImage: nDictType = 0; break;
+ case psp::ImageType::PaletteImage: nDictType = 1; break;
+ case psp::ImageType::GrayScaleImage: nDictType = 2; break;
+ case psp::ImageType::MonochromeImage: nDictType = 3; break;
+ default: break;
+ }
+
+ psp::getValueOf (rArea.GetWidth(), pImage);
+ psp::appendStr (" ", pImage);
+ psp::getValueOf (rArea.GetHeight(), pImage);
+ psp::appendStr (" ", pImage);
+ psp::getValueOf (nDictType, pImage);
+ psp::appendStr (" ", pImage);
+ psp::getValueOf (sal_Int32(1), pImage); // nCompressType
+ psp::appendStr (" psp_imagedict image\n", pImage);
+
+ WritePS (mpPageBody, pImage.makeStringAndClear());
+}
+
+void
+PrinterGfx::writePS2Colorspace(const PrinterBmp& rBitmap, psp::ImageType nType)
+{
+ switch (nType)
+ {
+ case psp::ImageType::GrayScaleImage:
+
+ WritePS (mpPageBody, "/DeviceGray setcolorspace\n");
+ break;
+
+ case psp::ImageType::TrueColorImage:
+
+ WritePS (mpPageBody, "/DeviceRGB setcolorspace\n");
+ break;
+
+ case psp::ImageType::MonochromeImage:
+ case psp::ImageType::PaletteImage:
+ {
+
+ OStringBuffer pImage;
+
+ const sal_uInt32 nSize = rBitmap.GetPaletteEntryCount();
+
+ psp::appendStr ("[/Indexed /DeviceRGB ", pImage);
+ psp::getValueOf (nSize - 1, pImage);
+ psp::appendStr ("\npsp_lzwstring\n", pImage);
+ WritePS (mpPageBody, pImage.makeStringAndClear());
+
+ LZWEncoder aEncoder(mpPageBody);
+ for (sal_uInt32 i = 0; i < nSize; i++)
+ {
+ PrinterColor aColor = rBitmap.GetPaletteColor(i);
+
+ aEncoder.EncodeByte (aColor.GetRed());
+ aEncoder.EncodeByte (aColor.GetGreen());
+ aEncoder.EncodeByte (aColor.GetBlue());
+ }
+
+ WritePS (mpPageBody, "pop ] setcolorspace\n");
+ }
+ break;
+ default: break;
+ }
+}
+
+void
+PrinterGfx::DrawPS2GrayImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea)
+{
+ writePS2Colorspace(rBitmap, psp::ImageType::GrayScaleImage);
+ writePS2ImageHeader(rArea, psp::ImageType::GrayScaleImage);
+
+ LZWEncoder aEncoder(mpPageBody);
+
+ for (tools::Long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
+ {
+ for (tools::Long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
+ {
+ unsigned char nByte = rBitmap.GetPixelGray (nRow, nColumn);
+ aEncoder.EncodeByte (nByte);
+ }
+ }
+}
+
+void
+PrinterGfx::DrawPS2MonoImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea)
+{
+ writePS2Colorspace(rBitmap, psp::ImageType::MonochromeImage);
+ writePS2ImageHeader(rArea, psp::ImageType::MonochromeImage);
+
+ LZWEncoder aEncoder(mpPageBody);
+
+ for (tools::Long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
+ {
+ tools::Long nBitPos = 0;
+ unsigned char nByte = 0;
+
+ for (tools::Long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
+ {
+ unsigned char nBit = rBitmap.GetPixelIdx (nRow, nColumn);
+ nByte |= nBit << (7 - nBitPos);
+
+ if (++nBitPos == 8)
+ {
+ aEncoder.EncodeByte (nByte);
+ nBitPos = 0;
+ nByte = 0;
+ }
+ }
+ // keep the row byte aligned
+ if (nBitPos != 0)
+ aEncoder.EncodeByte (nByte);
+ }
+}
+
+void
+PrinterGfx::DrawPS2PaletteImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea)
+{
+ writePS2Colorspace(rBitmap, psp::ImageType::PaletteImage);
+ writePS2ImageHeader(rArea, psp::ImageType::PaletteImage);
+
+ LZWEncoder aEncoder(mpPageBody);
+
+ for (tools::Long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
+ {
+ for (tools::Long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
+ {
+ unsigned char nByte = rBitmap.GetPixelIdx (nRow, nColumn);
+ aEncoder.EncodeByte (nByte);
+ }
+ }
+}
+
+void
+PrinterGfx::DrawPS2TrueColorImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea)
+{
+ writePS2Colorspace(rBitmap, psp::ImageType::TrueColorImage);
+ writePS2ImageHeader(rArea, psp::ImageType::TrueColorImage);
+
+ LZWEncoder aEncoder(mpPageBody);
+
+ for (tools::Long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
+ {
+ for (tools::Long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
+ {
+ PrinterColor aColor = rBitmap.GetPixelRGB (nRow, nColumn);
+ aEncoder.EncodeByte (aColor.GetRed());
+ aEncoder.EncodeByte (aColor.GetGreen());
+ aEncoder.EncodeByte (aColor.GetBlue());
+ }
+ }
+}
+
+} /* namespace psp */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/common_gfx.cxx b/vcl/unx/generic/print/common_gfx.cxx
new file mode 100644
index 000000000..aba50ece2
--- /dev/null
+++ b/vcl/unx/generic/print/common_gfx.cxx
@@ -0,0 +1,1152 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include "psputil.hxx"
+#include "glyphset.hxx"
+
+#include <unx/printergfx.hxx>
+#include <unx/printerjob.hxx>
+#include <unx/fontmanager.hxx>
+#include <strhelper.hxx>
+#include <printerinfomanager.hxx>
+
+#include <tools/color.hxx>
+#include <tools/poly.hxx>
+#include <tools/stream.hxx>
+#include <o3tl/string_view.hxx>
+
+using namespace psp ;
+
+const sal_Int32 nMaxTextColumn = 80;
+
+GraphicsStatus::GraphicsStatus() :
+ maEncoding(RTL_TEXTENCODING_DONTKNOW),
+ mbArtItalic( false ),
+ mbArtBold( false ),
+ mnTextHeight( 0 ),
+ mnTextWidth( 0 ),
+ mfLineWidth( -1 )
+{
+}
+
+/*
+ * non graphics routines
+ */
+
+void
+PrinterGfx::Init (PrinterJob &rPrinterJob)
+{
+ mpPageBody = rPrinterJob.GetCurrentPageBody ();
+ mnDepth = rPrinterJob.GetDepth ();
+ mnPSLevel = rPrinterJob.GetPostscriptLevel ();
+ mbColor = rPrinterJob.IsColorPrinter ();
+
+ mnDpi = rPrinterJob.GetResolution();
+ rPrinterJob.GetScale (mfScaleX, mfScaleY);
+ const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( rPrinterJob.GetPrinterName() ) );
+ mbUploadPS42Fonts = rInfo.m_pParser && rInfo.m_pParser->isType42Capable();
+}
+
+void
+PrinterGfx::Init (const JobData& rData)
+{
+ mpPageBody = nullptr;
+ mnDepth = rData.m_nColorDepth;
+ mnPSLevel = rData.m_nPSLevel ? rData.m_nPSLevel : (rData.m_pParser ? rData.m_pParser->getLanguageLevel() : 2 );
+ mbColor = rData.m_nColorDevice ? ( rData.m_nColorDevice != -1 ) : ( rData.m_pParser == nullptr || rData.m_pParser->isColorDevice() );
+ int nRes = rData.m_aContext.getRenderResolution();
+ mnDpi = nRes;
+ mfScaleX = 72.0 / static_cast<double>(mnDpi);
+ mfScaleY = 72.0 / static_cast<double>(mnDpi);
+ const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( rData.m_aPrinterName ) );
+ mbUploadPS42Fonts = rInfo.m_pParser && rInfo.m_pParser->isType42Capable();
+}
+
+
+PrinterGfx::PrinterGfx()
+ : mfScaleX(0.0)
+ , mfScaleY(0.0)
+ , mnDpi(0)
+ , mnDepth(0)
+ , mnPSLevel(0)
+ , mbColor(false)
+ , mbUploadPS42Fonts(false)
+ , mpPageBody(nullptr)
+ , mnFontID(0)
+ , mnTextAngle(0)
+ , mbTextVertical(false)
+ , mrFontMgr(PrintFontManager::get())
+ , maFillColor(0xff,0,0)
+ , maTextColor(0,0,0)
+ , maLineColor(0, 0xff, 0)
+{
+ maVirtualStatus.mfLineWidth = 1.0;
+ maVirtualStatus.mnTextHeight = 12;
+ maVirtualStatus.mnTextWidth = 0;
+
+ maGraphicsStack.emplace_back( );
+}
+
+PrinterGfx::~PrinterGfx()
+{
+}
+
+void
+PrinterGfx::Clear()
+{
+ mpPageBody = nullptr;
+ mnFontID = 0;
+ maVirtualStatus = GraphicsStatus();
+ maVirtualStatus.mnTextHeight = 12;
+ maVirtualStatus.mnTextWidth = 0;
+ maVirtualStatus.mfLineWidth = 1.0;
+ mbTextVertical = false;
+ maLineColor = PrinterColor();
+ maFillColor = PrinterColor();
+ maTextColor = PrinterColor();
+ mnDpi = 300;
+ mnDepth = 24;
+ mnPSLevel = 2;
+ mbColor = true;
+ mnTextAngle = 0_deg10;
+
+ maClipRegion.clear();
+ maGraphicsStack.clear();
+ maGraphicsStack.emplace_back( );
+}
+
+/*
+ * clip region handling
+ */
+
+void
+PrinterGfx::ResetClipRegion()
+{
+ maClipRegion.clear();
+ PSGRestore ();
+ PSGSave (); // get "clean" clippath
+}
+
+void
+PrinterGfx::BeginSetClipRegion()
+{
+ maClipRegion.clear();
+}
+
+void
+PrinterGfx::UnionClipRegion (sal_Int32 nX,sal_Int32 nY,sal_Int32 nDX,sal_Int32 nDY)
+{
+ if( nDX && nDY )
+ maClipRegion.emplace_back(Point(nX,nY ), Size(nDX,nDY));
+}
+
+bool
+PrinterGfx::JoinVerticalClipRectangles( std::list< tools::Rectangle >::iterator& it,
+ Point& rOldPoint, sal_Int32& rColumn )
+{
+ bool bSuccess = false;
+
+ std::list< tools::Rectangle >::iterator tempit, nextit;
+ nextit = it;
+ ++nextit;
+ std::list< Point > leftside, rightside;
+
+ tools::Rectangle aLastRect( *it );
+ leftside.emplace_back( it->Left(), it->Top() );
+ rightside.emplace_back( it->Right()+1, it->Top() );
+ while( nextit != maClipRegion.end() )
+ {
+ tempit = nextit;
+ ++tempit;
+ if( nextit->Top() == aLastRect.Bottom()+1 )
+ {
+ if(
+ ( nextit->Left() >= aLastRect.Left() && nextit->Left() <= aLastRect.Right() ) // left endpoint touches last rectangle
+ ||
+ ( nextit->Right() >= aLastRect.Left() && nextit->Right() <= aLastRect.Right() ) // right endpoint touches last rectangle
+ ||
+ ( nextit->Left() <= aLastRect.Left() && nextit->Right() >= aLastRect.Right() ) // whole line touches last rectangle
+ )
+ {
+ if( aLastRect.GetHeight() > 1 ||
+ std::abs( aLastRect.Left() - nextit->Left() ) > 2 ||
+ std::abs( aLastRect.Right() - nextit->Right() ) > 2
+ )
+ {
+ leftside.emplace_back( aLastRect.Left(), aLastRect.Bottom()+1 );
+ rightside.emplace_back( aLastRect.Right()+1, aLastRect.Bottom()+1 );
+ }
+ aLastRect = *nextit;
+ leftside.push_back( aLastRect.TopLeft() );
+ rightside.push_back( aLastRect.TopRight() );
+ maClipRegion.erase( nextit );
+ }
+ }
+ nextit = tempit;
+ }
+ if( leftside.size() > 1 )
+ {
+ // push the last coordinates
+ leftside.emplace_back( aLastRect.Left(), aLastRect.Bottom()+1 );
+ rightside.emplace_back( aLastRect.Right()+1, aLastRect.Bottom()+1 );
+
+ // cool, we can concatenate rectangles
+ const int nDX = -65536, nDY = 65536;
+ int nNewDX = 0, nNewDY = 0;
+
+ Point aLastPoint = leftside.front();
+ PSBinMoveTo (aLastPoint, rOldPoint, rColumn);
+ leftside.pop_front();
+ while( !leftside.empty() )
+ {
+ Point aPoint (leftside.front());
+ leftside.pop_front();
+ // may have been the last one
+ if( !leftside.empty() )
+ {
+ nNewDX = aPoint.X() - aLastPoint.X();
+ nNewDY = aPoint.Y() - aLastPoint.Y();
+ if( nNewDX != 0 &&
+ static_cast<double>(nNewDY)/static_cast<double>(nNewDX) == double(nDY)/double(nDX) )
+ continue;
+ }
+ PSBinLineTo (aPoint, rOldPoint, rColumn);
+ aLastPoint = aPoint;
+ }
+
+ aLastPoint = rightside.back();
+ PSBinLineTo (aLastPoint, rOldPoint, rColumn);
+ rightside.pop_back();
+ while( !rightside.empty() )
+ {
+ Point aPoint (rightside.back());
+ rightside.pop_back();
+ if( !rightside.empty() )
+ {
+ nNewDX = aPoint.X() - aLastPoint.X();
+ nNewDY = aPoint.Y() - aLastPoint.Y();
+ if( nNewDX != 0 &&
+ static_cast<double>(nNewDY)/static_cast<double>(nNewDX) == double(nDY)/double(nDX) )
+ continue;
+ }
+ PSBinLineTo (aPoint, rOldPoint, rColumn);
+ }
+
+ tempit = it;
+ ++tempit;
+ maClipRegion.erase( it );
+ it = tempit;
+ bSuccess = true;
+ }
+ return bSuccess;
+}
+
+void
+PrinterGfx::EndSetClipRegion()
+{
+ PSGRestore ();
+ PSGSave (); // get "clean" clippath
+
+ PSBinStartPath ();
+ Point aOldPoint (0, 0);
+ sal_Int32 nColumn = 0;
+
+ std::list< tools::Rectangle >::iterator it = maClipRegion.begin();
+ while( it != maClipRegion.end() )
+ {
+ // try to concatenate adjacent rectangles
+ // first try in y direction, then in x direction
+ if( ! JoinVerticalClipRectangles( it, aOldPoint, nColumn ) )
+ {
+ // failed, so it is a single rectangle
+ PSBinMoveTo (Point( it->Left()-1, it->Top()-1), aOldPoint, nColumn );
+ PSBinLineTo (Point( it->Left()-1, it->Bottom()+1 ), aOldPoint, nColumn );
+ PSBinLineTo (Point( it->Right()+1, it->Bottom()+1 ), aOldPoint, nColumn );
+ PSBinLineTo (Point( it->Right()+1, it->Top()-1 ), aOldPoint, nColumn );
+ ++it;
+ }
+ }
+
+ PSBinEndPath ();
+
+ WritePS (mpPageBody, "closepath clip newpath\n");
+ maClipRegion.clear();
+}
+
+/*
+ * draw graphic primitives
+ */
+
+void
+PrinterGfx::DrawRect (const tools::Rectangle& rRectangle )
+{
+ OStringBuffer pRect;
+
+ psp::getValueOf (rRectangle.Left(), pRect);
+ psp::appendStr (" ", pRect);
+ psp::getValueOf (rRectangle.Top(), pRect);
+ psp::appendStr (" ", pRect);
+ psp::getValueOf (rRectangle.GetWidth(), pRect);
+ psp::appendStr (" ", pRect);
+ psp::getValueOf (rRectangle.GetHeight(), pRect);
+ psp::appendStr (" ", pRect);
+ auto const rect = pRect.makeStringAndClear();
+
+ if( maFillColor.Is() )
+ {
+ PSSetColor (maFillColor);
+ PSSetColor ();
+ WritePS (mpPageBody, rect);
+ WritePS (mpPageBody, "rectfill\n");
+ }
+ if( maLineColor.Is() )
+ {
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+ WritePS (mpPageBody, rect);
+ WritePS (mpPageBody, "rectstroke\n");
+ }
+}
+
+void
+PrinterGfx::DrawLine (const Point& rFrom, const Point& rTo)
+{
+ if( maLineColor.Is() )
+ {
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+
+ PSMoveTo (rFrom);
+ PSLineTo (rTo);
+ WritePS (mpPageBody, "stroke\n" );
+ }
+}
+
+void
+PrinterGfx::DrawPixel (const Point& rPoint, const PrinterColor& rPixelColor)
+{
+ if( rPixelColor.Is() )
+ {
+ PSSetColor (rPixelColor);
+ PSSetColor ();
+
+ PSMoveTo (rPoint);
+ PSLineTo (Point (rPoint.X ()+1, rPoint.Y ()));
+ PSLineTo (Point (rPoint.X ()+1, rPoint.Y ()+1));
+ PSLineTo (Point (rPoint.X (), rPoint.Y ()+1));
+ WritePS (mpPageBody, "fill\n" );
+ }
+}
+
+void
+PrinterGfx::DrawPolyLine (sal_uInt32 nPoints, const Point* pPath)
+{
+ if( maLineColor.Is() && nPoints && pPath )
+ {
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+
+ PSBinCurrentPath (nPoints, pPath);
+
+ WritePS (mpPageBody, "stroke\n" );
+ }
+}
+
+void
+PrinterGfx::DrawPolygon (sal_uInt32 nPoints, const Point* pPath)
+{
+ // premature end of operation
+ if (nPoints <= 0 || (pPath == nullptr) || !(maFillColor.Is() || maLineColor.Is()))
+ return;
+
+ // setup closed path
+ Point aPoint( 0, 0 );
+ sal_Int32 nColumn( 0 );
+
+ PSBinStartPath();
+ PSBinMoveTo( pPath[0], aPoint, nColumn );
+ for( unsigned int n = 1; n < nPoints; n++ )
+ PSBinLineTo( pPath[n], aPoint, nColumn );
+ if( pPath[0] != pPath[nPoints-1] )
+ PSBinLineTo( pPath[0], aPoint, nColumn );
+ PSBinEndPath();
+
+ // fill the polygon first, then draw the border, note that fill and
+ // stroke reset the currentpath
+
+ // if fill and stroke, save the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGSave();
+
+ if (maFillColor.Is ())
+ {
+ PSSetColor (maFillColor);
+ PSSetColor ();
+ WritePS (mpPageBody, "eofill\n");
+ }
+
+ // restore the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGRestore();
+
+ if (maLineColor.Is ())
+ {
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+ WritePS (mpPageBody, "stroke\n");
+ }
+}
+
+void
+PrinterGfx::DrawPolyPolygon (sal_uInt32 nPoly, const sal_uInt32* pSizes, const Point** pPaths )
+{
+ // sanity check
+ if ( !nPoly || !pPaths || !(maFillColor.Is() || maLineColor.Is()))
+ return;
+
+ // setup closed path
+ for( unsigned int i = 0; i < nPoly; i++ )
+ {
+ Point aPoint( 0, 0 );
+ sal_Int32 nColumn( 0 );
+
+ PSBinStartPath();
+ PSBinMoveTo( pPaths[i][0], aPoint, nColumn );
+ for( unsigned int n = 1; n < pSizes[i]; n++ )
+ PSBinLineTo( pPaths[i][n], aPoint, nColumn );
+ if( pPaths[i][0] != pPaths[i][pSizes[i]-1] )
+ PSBinLineTo( pPaths[i][0], aPoint, nColumn );
+ PSBinEndPath();
+ }
+
+ // if eofill and stroke, save the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGSave();
+
+ // first draw area
+ if( maFillColor.Is() )
+ {
+ PSSetColor (maFillColor);
+ PSSetColor ();
+ WritePS (mpPageBody, "eofill\n");
+ }
+
+ // restore the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGRestore();
+
+ // now draw outlines
+ if( maLineColor.Is() )
+ {
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+ WritePS (mpPageBody, "stroke\n");
+ }
+}
+
+/*
+ * Bezier Polygon Drawing methods.
+ */
+
+void
+PrinterGfx::DrawPolyLineBezier (sal_uInt32 nPoints, const Point* pPath, const PolyFlags* pFlgAry)
+{
+ const sal_uInt32 nBezString= 1024;
+ char pString[nBezString];
+
+ if ( nPoints <= 1 || !maLineColor.Is() || !pPath )
+ return;
+
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " moveto\n", sal_Int64(pPath[0].X()), sal_Int64(pPath[0].Y()));
+ WritePS(mpPageBody, pString);
+
+ // Handle the drawing of mixed lines mixed with curves
+ // - a normal point followed by a normal point is a line
+ // - a normal point followed by 2 control points and a normal point is a curve
+ for (unsigned int i=1; i<nPoints;)
+ {
+ if (pFlgAry[i] != PolyFlags::Control) //If the next point is a PolyFlags::Normal, we're drawing a line
+ {
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " lineto\n", sal_Int64(pPath[i].X()), sal_Int64(pPath[i].Y()));
+ i++;
+ }
+ else //Otherwise we're drawing a spline
+ {
+ if (i+2 >= nPoints)
+ return; //Error: wrong sequence of control/normal points somehow
+ if ((pFlgAry[i] == PolyFlags::Control) && (pFlgAry[i+1] == PolyFlags::Control) &&
+ (pFlgAry[i+2] != PolyFlags::Control))
+ {
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " curveto\n",
+ sal_Int64(pPath[i].X()), sal_Int64(pPath[i].Y()),
+ sal_Int64(pPath[i+1].X()), sal_Int64(pPath[i+1].Y()),
+ sal_Int64(pPath[i+2].X()), sal_Int64(pPath[i+2].Y()));
+ }
+ else
+ {
+ OSL_FAIL( "PrinterGfx::DrawPolyLineBezier: Strange output" );
+ }
+ i+=3;
+ }
+ WritePS(mpPageBody, pString);
+ }
+
+ // now draw outlines
+ WritePS (mpPageBody, "stroke\n");
+}
+
+void
+PrinterGfx::DrawPolygonBezier (sal_uInt32 nPoints, const Point* pPath, const PolyFlags* pFlgAry)
+{
+ const sal_uInt32 nBezString = 1024;
+ char pString[nBezString];
+ // premature end of operation
+ if (nPoints <= 0 || (pPath == nullptr) || !(maFillColor.Is() || maLineColor.Is()))
+ return;
+
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " moveto\n", sal_Int64(pPath[0].X()), sal_Int64(pPath[0].Y()));
+ WritePS(mpPageBody, pString); //Move to the starting point for the PolyPolygon
+ for (unsigned int i=1; i < nPoints;)
+ {
+ if (pFlgAry[i] != PolyFlags::Control)
+ {
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " lineto\n",
+ sal_Int64(pPath[i].X()), sal_Int64(pPath[i].Y()));
+ WritePS(mpPageBody, pString);
+ i++;
+ }
+ else
+ {
+ if (i+2 >= nPoints)
+ return; //Error: wrong sequence of control/normal points somehow
+ if ((pFlgAry[i] == PolyFlags::Control) && (pFlgAry[i+1] == PolyFlags::Control) &&
+ (pFlgAry[i+2] != PolyFlags::Control))
+ {
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " curveto\n",
+ sal_Int64(pPath[i].X()), sal_Int64(pPath[i].Y()),
+ sal_Int64(pPath[i+1].X()), sal_Int64(pPath[i+1].Y()),
+ sal_Int64(pPath[i+2].X()), sal_Int64(pPath[i+2].Y()));
+ WritePS(mpPageBody, pString);
+ }
+ else
+ {
+ OSL_FAIL( "PrinterGfx::DrawPolygonBezier: Strange output" );
+ }
+ i+=3;
+ }
+ }
+
+ // if fill and stroke, save the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGSave();
+
+ if (maFillColor.Is ())
+ {
+ PSSetColor (maFillColor);
+ PSSetColor ();
+ WritePS (mpPageBody, "eofill\n");
+ }
+
+ // restore the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGRestore();
+}
+
+void
+PrinterGfx::DrawPolyPolygonBezier (sal_uInt32 nPoly, const sal_uInt32 * pPoints, const Point* const * pPtAry, const PolyFlags* const* pFlgAry)
+{
+ const sal_uInt32 nBezString = 1024;
+ char pString[nBezString];
+ if ( !nPoly || !pPtAry || !pPoints || !(maFillColor.Is() || maLineColor.Is()))
+ return;
+
+ for (unsigned int i=0; i<nPoly;i++)
+ {
+ sal_uInt32 nPoints = pPoints[i];
+ // sanity check
+ if( nPoints == 0 || pPtAry[i] == nullptr )
+ continue;
+
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " moveto\n",
+ sal_Int64(pPtAry[i][0].X()), sal_Int64(pPtAry[i][0].Y())); //Move to the starting point
+ WritePS(mpPageBody, pString);
+ for (unsigned int j=1; j < nPoints;)
+ {
+ // if no flag array exists for this polygon, then it must be a regular
+ // polygon without beziers
+ if ( ! pFlgAry[i] || pFlgAry[i][j] != PolyFlags::Control)
+ {
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " lineto\n",
+ sal_Int64(pPtAry[i][j].X()), sal_Int64(pPtAry[i][j].Y()));
+ WritePS(mpPageBody, pString);
+ j++;
+ }
+ else
+ {
+ if (j+2 >= nPoints)
+ break; //Error: wrong sequence of control/normal points somehow
+ if ((pFlgAry[i][j] == PolyFlags::Control) && (pFlgAry[i][j+1] == PolyFlags::Control) && (pFlgAry[i][j+2] != PolyFlags::Control))
+ {
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " curveto\n",
+ sal_Int64(pPtAry[i][j].X()), sal_Int64(pPtAry[i][j].Y()),
+ sal_Int64(pPtAry[i][j+1].X()), sal_Int64(pPtAry[i][j+1].Y()),
+ sal_Int64(pPtAry[i][j+2].X()), sal_Int64(pPtAry[i][j+2].Y()));
+ WritePS(mpPageBody, pString);
+ }
+ else
+ {
+ OSL_FAIL( "PrinterGfx::DrawPolyPolygonBezier: Strange output" );
+ }
+ j+=3;
+ }
+ }
+ }
+
+ // if fill and stroke, save the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGSave();
+
+ if (maFillColor.Is ())
+ {
+ PSSetColor (maFillColor);
+ PSSetColor ();
+ WritePS (mpPageBody, "eofill\n");
+ }
+
+ // restore the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGRestore();
+}
+
+/*
+ * postscript generating routines
+ */
+void
+PrinterGfx::PSGSave ()
+{
+ WritePS (mpPageBody, "gsave\n" );
+ GraphicsStatus aNewState;
+ if( !maGraphicsStack.empty() )
+ aNewState = maGraphicsStack.front();
+ maGraphicsStack.push_front( aNewState );
+}
+
+void
+PrinterGfx::PSGRestore ()
+{
+ WritePS (mpPageBody, "grestore\n" );
+ if( maGraphicsStack.empty() )
+ WritePS (mpPageBody, "Error: too many grestores\n" );
+ else
+ maGraphicsStack.pop_front();
+}
+
+void
+PrinterGfx::PSSetLineWidth ()
+{
+ if( currentState().mfLineWidth != maVirtualStatus.mfLineWidth )
+ {
+ OStringBuffer pBuffer;
+
+ currentState().mfLineWidth = maVirtualStatus.mfLineWidth;
+ psp::getValueOfDouble (pBuffer, maVirtualStatus.mfLineWidth, 5);
+ psp::appendStr (" setlinewidth\n", pBuffer);
+ WritePS (mpPageBody, pBuffer.makeStringAndClear());
+ }
+}
+
+void
+PrinterGfx::PSSetColor ()
+{
+ PrinterColor& rColor( maVirtualStatus.maColor );
+
+ if( currentState().maColor == rColor )
+ return;
+
+ currentState().maColor = rColor;
+
+ OStringBuffer pBuffer;
+
+ if( mbColor )
+ {
+ psp::getValueOfDouble (pBuffer,
+ static_cast<double>(rColor.GetRed()) / 255.0, 5);
+ psp::appendStr (" ", pBuffer);
+ psp::getValueOfDouble (pBuffer,
+ static_cast<double>(rColor.GetGreen()) / 255.0, 5);
+ psp::appendStr (" ", pBuffer);
+ psp::getValueOfDouble (pBuffer,
+ static_cast<double>(rColor.GetBlue()) / 255.0, 5);
+ psp::appendStr (" setrgbcolor\n", pBuffer );
+ }
+ else
+ {
+ Color aColor( rColor.GetRed(), rColor.GetGreen(), rColor.GetBlue() );
+ sal_uInt8 nCol = aColor.GetLuminance();
+ psp::getValueOfDouble( pBuffer, static_cast<double>(nCol) / 255.0, 5 );
+ psp::appendStr( " setgray\n", pBuffer );
+ }
+
+ WritePS (mpPageBody, pBuffer.makeStringAndClear());
+}
+
+void
+PrinterGfx::PSSetFont ()
+{
+ GraphicsStatus& rCurrent( currentState() );
+ if( !(maVirtualStatus.maFont != rCurrent.maFont ||
+ maVirtualStatus.mnTextHeight != rCurrent.mnTextHeight ||
+ maVirtualStatus.maEncoding != rCurrent.maEncoding ||
+ maVirtualStatus.mnTextWidth != rCurrent.mnTextWidth ||
+ maVirtualStatus.mbArtBold != rCurrent.mbArtBold ||
+ maVirtualStatus.mbArtItalic != rCurrent.mbArtItalic)
+ )
+ return;
+
+ rCurrent.maFont = maVirtualStatus.maFont;
+ rCurrent.maEncoding = maVirtualStatus.maEncoding;
+ rCurrent.mnTextWidth = maVirtualStatus.mnTextWidth;
+ rCurrent.mnTextHeight = maVirtualStatus.mnTextHeight;
+ rCurrent.mbArtItalic = maVirtualStatus.mbArtItalic;
+ rCurrent.mbArtBold = maVirtualStatus.mbArtBold;
+
+ sal_Int32 nTextHeight = rCurrent.mnTextHeight;
+ sal_Int32 nTextWidth = rCurrent.mnTextWidth ? rCurrent.mnTextWidth
+ : rCurrent.mnTextHeight;
+
+ OStringBuffer pSetFont;
+
+ // postscript based fonts need reencoding
+ if ( ( rCurrent.maEncoding == RTL_TEXTENCODING_MS_1252)
+ || ( rCurrent.maEncoding == RTL_TEXTENCODING_ISO_8859_1)
+ || ( rCurrent.maEncoding >= RTL_TEXTENCODING_USER_START
+ && rCurrent.maEncoding <= RTL_TEXTENCODING_USER_END)
+ )
+ {
+ OString aReencodedFont =
+ psp::GlyphSet::GetReencodedFontName (rCurrent.maEncoding,
+ rCurrent.maFont);
+
+ psp::appendStr ("(", pSetFont);
+ psp::appendStr (aReencodedFont.getStr(),
+ pSetFont);
+ psp::appendStr (") cvn findfont ",
+ pSetFont);
+ }
+ else
+ // tt based fonts mustn't reencode, the encoding is implied by the fontname
+ // same for symbol type1 fonts, don't try to touch them
+ {
+ psp::appendStr ("(", pSetFont);
+ psp::appendStr (rCurrent.maFont.getStr(),
+ pSetFont);
+ psp::appendStr (") cvn findfont ",
+ pSetFont);
+ }
+
+ if( ! rCurrent.mbArtItalic )
+ {
+ psp::getValueOf (nTextWidth, pSetFont);
+ psp::appendStr (" ", pSetFont);
+ psp::getValueOf (-nTextHeight, pSetFont);
+ psp::appendStr (" matrix scale makefont setfont\n", pSetFont);
+ }
+ else // skew 15 degrees to right
+ {
+ psp::appendStr ( " [", pSetFont);
+ psp::getValueOf (nTextWidth, pSetFont);
+ psp::appendStr (" 0 ", pSetFont);
+ psp::getValueOfDouble (pSetFont, 0.27*static_cast<double>(nTextWidth), 3 );
+ psp::appendStr ( " ", pSetFont);
+ psp::getValueOf (-nTextHeight, pSetFont);
+
+ psp::appendStr (" 0 0] makefont setfont\n", pSetFont);
+ }
+
+ WritePS (mpPageBody, pSetFont.makeStringAndClear());
+
+}
+
+void
+PrinterGfx::PSRotate (Degree10 nAngle)
+{
+ sal_Int32 nPostScriptAngle = -nAngle.get();
+ while( nPostScriptAngle < 0 )
+ nPostScriptAngle += 3600;
+
+ if (nPostScriptAngle == 0)
+ return;
+
+ sal_Int32 nFullAngle = nPostScriptAngle / 10;
+ sal_Int32 nTenthAngle = nPostScriptAngle % 10;
+
+ OStringBuffer pRotate;
+
+ psp::getValueOf (nFullAngle, pRotate);
+ psp::appendStr (".", pRotate);
+ psp::getValueOf (nTenthAngle, pRotate);
+ psp::appendStr (" rotate\n", pRotate);
+
+ WritePS (mpPageBody, pRotate.makeStringAndClear());
+}
+
+void
+PrinterGfx::PSPointOp (const Point& rPoint, const char* pOperator)
+{
+ OStringBuffer pPSCommand;
+
+ psp::getValueOf (rPoint.X(), pPSCommand);
+ psp::appendStr (" ", pPSCommand);
+ psp::getValueOf (rPoint.Y(), pPSCommand);
+ psp::appendStr (" ", pPSCommand);
+ psp::appendStr (pOperator, pPSCommand);
+ psp::appendStr ("\n", pPSCommand);
+
+ WritePS (mpPageBody, pPSCommand.makeStringAndClear());
+}
+
+void
+PrinterGfx::PSTranslate (const Point& rPoint)
+{
+ PSPointOp (rPoint, "translate");
+}
+
+void
+PrinterGfx::PSMoveTo (const Point& rPoint)
+{
+ PSPointOp (rPoint, "moveto");
+}
+
+void
+PrinterGfx::PSLineTo (const Point& rPoint)
+{
+ PSPointOp (rPoint, "lineto");
+}
+
+/* get a compressed representation of the path information */
+
+#define DEBUG_BINPATH 0
+
+void
+PrinterGfx::PSBinLineTo (const Point& rCurrent, Point& rOld, sal_Int32& nColumn)
+{
+#if (DEBUG_BINPATH == 1)
+ PSLineTo (rCurrent);
+#else
+ PSBinPath (rCurrent, rOld, lineto, nColumn);
+#endif
+}
+
+void
+PrinterGfx::PSBinMoveTo (const Point& rCurrent, Point& rOld, sal_Int32& nColumn)
+{
+#if (DEBUG_BINPATH == 1)
+ PSMoveTo (rCurrent);
+#else
+ PSBinPath (rCurrent, rOld, moveto, nColumn);
+#endif
+}
+
+void
+PrinterGfx::PSBinStartPath ()
+{
+#if (DEBUG_BINPATH == 1)
+ WritePS (mpPageBody, "% PSBinStartPath\n");
+#else
+ WritePS (mpPageBody, "readpath\n" );
+#endif
+}
+
+void
+PrinterGfx::PSBinEndPath ()
+{
+#if (DEBUG_BINPATH == 1)
+ WritePS (mpPageBody, "% PSBinEndPath\n");
+#else
+ WritePS (mpPageBody, "~\n");
+#endif
+}
+
+void
+PrinterGfx::PSBinCurrentPath (sal_uInt32 nPoints, const Point* pPath)
+{
+ // create the path
+ Point aPoint (0, 0);
+ sal_Int32 nColumn = 0;
+
+ PSBinStartPath ();
+ PSBinMoveTo (*pPath, aPoint, nColumn);
+ for (unsigned int i = 1; i < nPoints; i++)
+ PSBinLineTo (pPath[i], aPoint, nColumn);
+ PSBinEndPath ();
+}
+
+void
+PrinterGfx::PSBinPath (const Point& rCurrent, Point& rOld,
+ pspath_t eType, sal_Int32& nColumn)
+{
+ OStringBuffer pPath;
+ sal_Int32 nChar;
+
+ // create the hex representation of the dx and dy path shift, store the field
+ // width as it is needed for the building the command
+ sal_Int32 nXPrec = getAlignedHexValueOf (rCurrent.X() - rOld.X(), pPath);
+ sal_Int32 nYPrec = getAlignedHexValueOf (rCurrent.Y() - rOld.Y(), pPath);
+
+ // build the command, it is a char with bit representation 000cxxyy
+ // c represents the char, xx and yy repr. the field width of the dx and dy shift,
+ // dx and dy represent the number of bytes to read after the opcode
+ char cCmd = (eType == lineto ? char(0x00) : char(0x10));
+ switch (nYPrec)
+ {
+ case 2: break;
+ case 4: cCmd |= 0x01; break;
+ case 6: cCmd |= 0x02; break;
+ case 8: cCmd |= 0x03; break;
+ default: OSL_FAIL("invalid x precision in binary path");
+ }
+ switch (nXPrec)
+ {
+ case 2: break;
+ case 4: cCmd |= 0x04; break;
+ case 6: cCmd |= 0x08; break;
+ case 8: cCmd |= 0x0c; break;
+ default: OSL_FAIL("invalid y precision in binary path");
+ }
+ cCmd += 'A';
+ pPath.insert(0, cCmd);
+ auto const path = pPath.makeStringAndClear();
+
+ // write the command to file,
+ // line breaking at column nMaxTextColumn (80)
+ nChar = 1 + nXPrec + nYPrec;
+ if ((nColumn + nChar) > nMaxTextColumn)
+ {
+ sal_Int32 nSegment = nMaxTextColumn - nColumn;
+
+ WritePS (mpPageBody, path.copy(0, nSegment));
+ WritePS (mpPageBody, "\n", 1);
+ WritePS (mpPageBody, path.copy(nSegment));
+
+ nColumn = nChar - nSegment;
+ }
+ else
+ {
+ WritePS (mpPageBody, path);
+
+ nColumn += nChar;
+ }
+
+ rOld = rCurrent;
+}
+
+void
+PrinterGfx::PSScale (double fScaleX, double fScaleY)
+{
+ OStringBuffer pScale;
+
+ psp::getValueOfDouble (pScale, fScaleX, 5);
+ psp::appendStr (" ", pScale);
+ psp::getValueOfDouble (pScale, fScaleY, 5);
+ psp::appendStr (" scale\n", pScale);
+
+ WritePS (mpPageBody, pScale.makeStringAndClear());
+}
+
+/* psshowtext helper routines: draw an hex string for show/xshow */
+void
+PrinterGfx::PSHexString (const unsigned char* pString, sal_Int16 nLen)
+{
+ OStringBuffer pHexString;
+ sal_Int32 nChar = psp::appendStr ("<", pHexString);
+ for (int i = 0; i < nLen; i++)
+ {
+ if (nChar >= (nMaxTextColumn - 1))
+ {
+ psp::appendStr ("\n", pHexString);
+ WritePS (mpPageBody, pHexString.makeStringAndClear());
+ nChar = 0;
+ }
+ nChar += psp::getHexValueOf (static_cast<sal_Int32>(pString[i]), pHexString);
+ }
+
+ psp::appendStr (">\n", pHexString);
+ WritePS (mpPageBody, pHexString.makeStringAndClear());
+}
+
+void
+PrinterGfx::PSShowGlyph (const unsigned char nGlyphId)
+{
+ PSSetColor (maTextColor);
+ PSSetColor ();
+ PSSetFont ();
+ // rotate the user coordinate system
+ if (mnTextAngle)
+ {
+ PSGSave ();
+ PSRotate (mnTextAngle);
+ }
+
+ char pBuffer[256];
+ if( maVirtualStatus.mbArtBold )
+ {
+ sal_Int32 nLW = maVirtualStatus.mnTextWidth;
+ if( nLW == 0 )
+ nLW = maVirtualStatus.mnTextHeight;
+ else
+ nLW = std::min(nLW, maVirtualStatus.mnTextHeight);
+ psp::getValueOfDouble( pBuffer, static_cast<double>(nLW) / 30.0 );
+ }
+
+ // dispatch to the drawing method
+ PSHexString (&nGlyphId, 1);
+
+ if( maVirtualStatus.mbArtBold )
+ {
+ WritePS( mpPageBody, pBuffer );
+ WritePS( mpPageBody, " bshow\n" );
+ }
+ else
+ WritePS (mpPageBody, "show\n");
+
+ // restore the user coordinate system
+ if (mnTextAngle)
+ PSGRestore ();
+}
+
+bool
+PrinterGfx::DrawEPS( const tools::Rectangle& rBoundingBox, void* pPtr, sal_uInt32 nSize )
+{
+ if( nSize == 0 )
+ return true;
+ if( ! mpPageBody )
+ return false;
+
+ bool bSuccess = false;
+
+ // first search the BoundingBox of the EPS data
+ SvMemoryStream aStream( pPtr, nSize, StreamMode::READ );
+ aStream.Seek( STREAM_SEEK_TO_BEGIN );
+ OString aLine;
+
+ OString aDocTitle;
+ double fLeft = 0, fRight = 0, fTop = 0, fBottom = 0;
+ bool bEndComments = false;
+ while( ! aStream.eof()
+ && ( ( fLeft == 0 && fRight == 0 && fTop == 0 && fBottom == 0 ) ||
+ ( aDocTitle.isEmpty() && !bEndComments ) )
+ )
+ {
+ aStream.ReadLine( aLine );
+ if( aLine.getLength() > 1 && aLine[0] == '%' )
+ {
+ char cChar = aLine[1];
+ if( cChar == '%' )
+ {
+ if( aLine.matchIgnoreAsciiCase( "%%BoundingBox:" ) )
+ {
+ aLine = WhitespaceToSpace( o3tl::getToken(aLine, 1, ':') );
+ if( !aLine.isEmpty() && aLine.indexOf( "atend" ) == -1 )
+ {
+ fLeft = StringToDouble( GetCommandLineToken( 0, aLine ) );
+ fBottom = StringToDouble( GetCommandLineToken( 1, aLine ) );
+ fRight = StringToDouble( GetCommandLineToken( 2, aLine ) );
+ fTop = StringToDouble( GetCommandLineToken( 3, aLine ) );
+ }
+ }
+ else if( aLine.matchIgnoreAsciiCase( "%%Title:" ) )
+ aDocTitle = WhitespaceToSpace( aLine.subView( 8 ) );
+ else if( aLine.matchIgnoreAsciiCase( "%%EndComments" ) )
+ bEndComments = true;
+ }
+ else if( cChar == ' ' || cChar == '\t' || cChar == '\r' || cChar == '\n' )
+ bEndComments = true;
+ }
+ else
+ bEndComments = true;
+ }
+
+ static sal_uInt16 nEps = 0;
+ if( aDocTitle.isEmpty() )
+ aDocTitle = OString::number(nEps++);
+
+ if( fLeft != fRight && fTop != fBottom )
+ {
+ double fScaleX = static_cast<double>(rBoundingBox.GetWidth())/(fRight-fLeft);
+ double fScaleY = -static_cast<double>(rBoundingBox.GetHeight())/(fTop-fBottom);
+ Point aTranslatePoint( static_cast<int>(rBoundingBox.Left()-fLeft*fScaleX),
+ static_cast<int>(rBoundingBox.Bottom()+1-fBottom*fScaleY) );
+ // prepare EPS
+ WritePS( mpPageBody,
+ "/b4_Inc_state save def\n"
+ "/dict_count countdictstack def\n"
+ "/op_count count 1 sub def\n"
+ "userdict begin\n"
+ "/showpage {} def\n"
+ "0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin\n"
+ "10 setmiterlimit [] 0 setdash newpath\n"
+ "/languagelevel where\n"
+ "{pop languagelevel\n"
+ "1 ne\n"
+ " {false setstrokeadjust false setoverprint\n"
+ " } if\n"
+ "}if\n" );
+ // set up clip path and scale
+ BeginSetClipRegion();
+ UnionClipRegion( rBoundingBox.Left(), rBoundingBox.Top(), rBoundingBox.GetWidth(), rBoundingBox.GetHeight() );
+ EndSetClipRegion();
+ PSTranslate( aTranslatePoint );
+ PSScale( fScaleX, fScaleY );
+
+ // DSC requires BeginDocument
+ WritePS( mpPageBody, "%%BeginDocument: " );
+ WritePS( mpPageBody, aDocTitle );
+ WritePS( mpPageBody, "\n" );
+
+ // write the EPS data
+ sal_uInt64 nOutLength;
+ mpPageBody->write( pPtr, nSize, nOutLength );
+ bSuccess = nOutLength == nSize;
+
+ // corresponding EndDocument
+ if( static_cast<char*>(pPtr)[ nSize-1 ] != '\n' )
+ WritePS( mpPageBody, "\n" );
+ WritePS( mpPageBody, "%%EndDocument\n" );
+
+ // clean up EPS
+ WritePS( mpPageBody,
+ "count op_count sub {pop} repeat\n"
+ "countdictstack dict_count sub {end} repeat\n"
+ "b4_Inc_state restore\n" );
+ }
+ return bSuccess;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/genprnpsp.cxx b/vcl/unx/generic/print/genprnpsp.cxx
new file mode 100644
index 000000000..b84ba0bef
--- /dev/null
+++ b/vcl/unx/generic/print/genprnpsp.cxx
@@ -0,0 +1,1298 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/**
+ this file implements the sal printer interface (SalPrinter, SalInfoPrinter
+ and some printer relevant methods of SalInstance and SalGraphicsData)
+
+ as underlying library the printer features of psprint are used.
+
+ The query methods of a SalInfoPrinter are implemented by querying psprint
+
+ The job methods of a SalPrinter are implemented by calling psprint
+ printer job functions.
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+// For spawning PDF and FAX generation
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <comphelper/fileurl.hxx>
+#include <o3tl/safeint.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/printer/Options.hxx>
+#include <vcl/print.hxx>
+#include <vcl/QueueInfo.hxx>
+#include <vcl/pdfwriter.hxx>
+#include <printerinfomanager.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/weld.hxx>
+#include <strings.hrc>
+#include <unx/genprn.h>
+#include <unx/geninst.h>
+#include <unx/genpspgraphics.h>
+
+#include <jobset.h>
+#include <print.h>
+#include "prtsetup.hxx"
+#include <salptype.hxx>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+
+using namespace psp;
+using namespace com::sun::star;
+
+static bool getPdfDir( const PrinterInfo& rInfo, OUString &rDir )
+{
+ sal_Int32 nIndex = 0;
+ while( nIndex != -1 )
+ {
+ OUString aToken( rInfo.m_aFeatures.getToken( 0, ',', nIndex ) );
+ if( aToken.startsWith( "pdf=" ) )
+ {
+ sal_Int32 nPos = 0;
+ rDir = aToken.getToken( 1, '=', nPos );
+ if( rDir.isEmpty() && getenv( "HOME" ) )
+ rDir = OUString( getenv( "HOME" ), strlen( getenv( "HOME" ) ), osl_getThreadTextEncoding() );
+ return true;
+ }
+ }
+ return false;
+}
+
+namespace
+{
+ class QueryString : public weld::GenericDialogController
+ {
+ private:
+ OUString& m_rReturnValue;
+
+ std::unique_ptr<weld::Button> m_xOKButton;
+ std::unique_ptr<weld::Label> m_xFixedText;
+ std::unique_ptr<weld::Entry> m_xEdit;
+
+ DECL_LINK( ClickBtnHdl, weld::Button&, void );
+
+ public:
+ // parent window, Query text, initial value
+ QueryString(weld::Window*, OUString const &, OUString &);
+ };
+
+ /*
+ * QueryString
+ */
+ QueryString::QueryString(weld::Window* pParent, OUString const & rQuery, OUString& rRet)
+ : GenericDialogController(pParent, "vcl/ui/querydialog.ui", "QueryDialog")
+ , m_rReturnValue( rRet )
+ , m_xOKButton(m_xBuilder->weld_button("ok"))
+ , m_xFixedText(m_xBuilder->weld_label("label"))
+ , m_xEdit(m_xBuilder->weld_entry("entry"))
+ {
+ m_xOKButton->connect_clicked(LINK(this, QueryString, ClickBtnHdl));
+ m_xFixedText->set_label(rQuery);
+ m_xEdit->set_text(m_rReturnValue);
+ m_xDialog->set_title(rQuery);
+ }
+
+ IMPL_LINK(QueryString, ClickBtnHdl, weld::Button&, rButton, void)
+ {
+ if (&rButton == m_xOKButton.get())
+ {
+ m_rReturnValue = m_xEdit->get_text();
+ m_xDialog->response(RET_OK);
+ }
+ else
+ m_xDialog->response(RET_CANCEL);
+ }
+
+ int QueryFaxNumber(OUString& rNumber)
+ {
+ QueryString aQuery(Application::GetDefDialogParent(), VclResId(SV_PRINT_QUERYFAXNUMBER_TXT), rNumber);
+ return aQuery.run();
+ }
+}
+
+static int PtTo10Mu( int nPoints ) { return static_cast<int>((static_cast<double>(nPoints)*35.27777778)+0.5); }
+
+static int TenMuToPt( int nUnits ) { return static_cast<int>((static_cast<double>(nUnits)/35.27777778)+0.5); }
+
+static void copyJobDataToJobSetup( ImplJobSetup* pJobSetup, JobData& rData )
+{
+ pJobSetup->SetOrientation( rData.m_eOrientation == orientation::Landscape ?
+ Orientation::Landscape : Orientation::Portrait );
+
+ // copy page size
+ OUString aPaper;
+ int width, height;
+
+ rData.m_aContext.getPageSize( aPaper, width, height );
+ pJobSetup->SetPaperFormat( PaperInfo::fromPSName(
+ OUStringToOString( aPaper, RTL_TEXTENCODING_ISO_8859_1 )));
+
+ pJobSetup->SetPaperWidth( 0 );
+ pJobSetup->SetPaperHeight( 0 );
+ if( pJobSetup->GetPaperFormat() == PAPER_USER )
+ {
+ // transform to 100dth mm
+ width = PtTo10Mu( width );
+ height = PtTo10Mu( height );
+
+ if( rData.m_eOrientation == psp::orientation::Portrait )
+ {
+ pJobSetup->SetPaperWidth( width );
+ pJobSetup->SetPaperHeight( height );
+ }
+ else
+ {
+ pJobSetup->SetPaperWidth( height );
+ pJobSetup->SetPaperHeight( width );
+ }
+ }
+
+ // copy input slot
+ const PPDKey* pKey = nullptr;
+ const PPDValue* pValue = nullptr;
+
+ pJobSetup->SetPaperBin( 0 );
+ if( rData.m_pParser )
+ pKey = rData.m_pParser->getKey( "InputSlot" );
+ if( pKey )
+ pValue = rData.m_aContext.getValue( pKey );
+ if( pKey && pValue )
+ {
+ int nPaperBin;
+ for( nPaperBin = 0;
+ pValue != pKey->getValue( nPaperBin ) &&
+ nPaperBin < pKey->countValues();
+ nPaperBin++);
+ pJobSetup->SetPaperBin(
+ nPaperBin == pKey->countValues() ? 0 : nPaperBin);
+ }
+
+ // copy duplex
+ pKey = nullptr;
+ pValue = nullptr;
+
+ pJobSetup->SetDuplexMode( DuplexMode::Unknown );
+ if( rData.m_pParser )
+ pKey = rData.m_pParser->getKey( "Duplex" );
+ if( pKey )
+ pValue = rData.m_aContext.getValue( pKey );
+ if( pKey && pValue )
+ {
+ if( pValue->m_aOption.equalsIgnoreAsciiCase( "None" ) ||
+ pValue->m_aOption.startsWithIgnoreAsciiCase( "Simplex" )
+ )
+ {
+ pJobSetup->SetDuplexMode( DuplexMode::Off);
+ }
+ else if( pValue->m_aOption.equalsIgnoreAsciiCase( "DuplexNoTumble" ) )
+ {
+ pJobSetup->SetDuplexMode( DuplexMode::LongEdge );
+ }
+ else if( pValue->m_aOption.equalsIgnoreAsciiCase( "DuplexTumble" ) )
+ {
+ pJobSetup->SetDuplexMode( DuplexMode::ShortEdge );
+ }
+ }
+
+ // copy the whole context
+ if( pJobSetup->GetDriverData() )
+ std::free( const_cast<sal_uInt8*>(pJobSetup->GetDriverData()) );
+
+ sal_uInt32 nBytes;
+ void* pBuffer = nullptr;
+ if( rData.getStreamBuffer( pBuffer, nBytes ) )
+ {
+ pJobSetup->SetDriverDataLen( nBytes );
+ pJobSetup->SetDriverData( static_cast<sal_uInt8*>(pBuffer) );
+ }
+ else
+ {
+ pJobSetup->SetDriverDataLen( 0 );
+ pJobSetup->SetDriverData( nullptr );
+ }
+ pJobSetup->SetPapersizeFromSetup( rData.m_bPapersizeFromSetup );
+}
+
+// Needs a cleaner abstraction ...
+static bool passFileToCommandLine( const OUString& rFilename, const OUString& rCommandLine )
+{
+ bool bSuccess = false;
+
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OString aCmdLine(OUStringToOString(rCommandLine, aEncoding));
+ OString aFilename(OUStringToOString(rFilename, aEncoding));
+
+ bool bPipe = aCmdLine.indexOf( "(TMP)" ) == -1;
+
+ // setup command line for exec
+ if( ! bPipe )
+ aCmdLine = aCmdLine.replaceAll("(TMP)", aFilename);
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", (bPipe ? "piping to" : "executing")
+ << " commandline: \"" << aCmdLine << "\".");
+ struct stat aStat;
+ SAL_WARN_IF(stat( aFilename.getStr(), &aStat ),
+ "vcl.unx.print", "stat( " << aFilename << " ) failed.");
+ SAL_INFO("vcl.unx.print", "Tmp file " << aFilename
+ << " has modes: "
+ << std::showbase << std::oct
+ << (long)aStat.st_mode);
+#endif
+ const char* argv[4];
+ if( ! ( argv[ 0 ] = getenv( "SHELL" ) ) )
+ argv[ 0 ] = "/bin/sh";
+ argv[ 1 ] = "-c";
+ argv[ 2 ] = aCmdLine.getStr();
+ argv[ 3 ] = nullptr;
+
+ bool bHavePipes = false;
+ int pid, fd[2];
+
+ if( bPipe )
+ bHavePipes = pipe( fd ) == 0;
+ if( ( pid = fork() ) > 0 )
+ {
+ if( bPipe && bHavePipes )
+ {
+ close( fd[0] );
+ char aBuffer[ 2048 ];
+ FILE* fp = fopen( aFilename.getStr(), "r" );
+ while (fp && !feof(fp))
+ {
+ size_t nBytesRead = fread(aBuffer, 1, sizeof( aBuffer ), fp);
+ if (nBytesRead )
+ {
+ size_t nBytesWritten = write(fd[1], aBuffer, nBytesRead);
+ OSL_ENSURE(nBytesWritten == nBytesRead, "short write");
+ if (nBytesWritten != nBytesRead)
+ break;
+ }
+ }
+ fclose( fp );
+ close( fd[ 1 ] );
+ }
+ int status = 0;
+ if(waitpid( pid, &status, 0 ) != -1)
+ {
+ if( ! status )
+ bSuccess = true;
+ }
+ }
+ else if( ! pid )
+ {
+ if( bPipe && bHavePipes )
+ {
+ close( fd[1] );
+ if( fd[0] != STDIN_FILENO ) // not probable, but who knows :)
+ dup2( fd[0], STDIN_FILENO );
+ }
+ execv( argv[0], const_cast<char**>(argv) );
+ fprintf( stderr, "failed to execute \"%s\"\n", aCmdLine.getStr() );
+ _exit( 1 );
+ }
+ else
+ fprintf( stderr, "failed to fork\n" );
+
+ // clean up the mess
+ unlink( aFilename.getStr() );
+
+ return bSuccess;
+}
+
+static std::vector<OUString> getFaxNumbers()
+{
+ std::vector<OUString> aFaxNumbers;
+
+ OUString aNewNr;
+ if (QueryFaxNumber(aNewNr))
+ {
+ for (sal_Int32 nIndex {0}; nIndex >= 0; )
+ aFaxNumbers.push_back(aNewNr.getToken( 0, ';', nIndex ));
+ }
+
+ return aFaxNumbers;
+}
+
+static bool createPdf( std::u16string_view rToFile, const OUString& rFromFile, const OUString& rCommandLine )
+{
+ return passFileToCommandLine( rFromFile, rCommandLine.replaceAll("(OUTFILE)", rToFile) );
+}
+
+/*
+ * SalInstance
+ */
+
+void SalGenericInstance::configurePspInfoPrinter(PspSalInfoPrinter *pPrinter,
+ SalPrinterQueueInfo const * pQueueInfo, ImplJobSetup* pJobSetup)
+{
+ if( !pJobSetup )
+ return;
+
+ PrinterInfoManager& rManager( PrinterInfoManager::get() );
+ PrinterInfo aInfo( rManager.getPrinterInfo( pQueueInfo->maPrinterName ) );
+ pPrinter->m_aJobData = aInfo;
+ pPrinter->m_aPrinterGfx.Init( pPrinter->m_aJobData );
+
+ if( pJobSetup->GetDriverData() )
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(),
+ pJobSetup->GetDriverDataLen(), aInfo );
+
+ pJobSetup->SetSystem( JOBSETUP_SYSTEM_UNIX );
+ pJobSetup->SetPrinterName( pQueueInfo->maPrinterName );
+ pJobSetup->SetDriver( aInfo.m_aDriverName );
+ copyJobDataToJobSetup( pJobSetup, aInfo );
+}
+
+SalInfoPrinter* SalGenericInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pJobSetup )
+{
+ mbPrinterInit = true;
+ // create and initialize SalInfoPrinter
+ PspSalInfoPrinter* pPrinter = new PspSalInfoPrinter();
+ configurePspInfoPrinter(pPrinter, pQueueInfo, pJobSetup);
+ return pPrinter;
+}
+
+void SalGenericInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter )
+{
+ delete pPrinter;
+}
+
+std::unique_ptr<SalPrinter> SalGenericInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
+{
+ mbPrinterInit = true;
+ // create and initialize SalPrinter
+ PspSalPrinter* pPrinter = new PspSalPrinter( pInfoPrinter );
+ pPrinter->m_aJobData = static_cast<PspSalInfoPrinter*>(pInfoPrinter)->m_aJobData;
+
+ return std::unique_ptr<SalPrinter>(pPrinter);
+}
+
+void SalGenericInstance::GetPrinterQueueInfo( ImplPrnQueueList* pList )
+{
+ mbPrinterInit = true;
+ PrinterInfoManager& rManager( PrinterInfoManager::get() );
+ static const char* pNoSyncDetection = getenv( "SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION" );
+ if( ! pNoSyncDetection || ! *pNoSyncDetection )
+ {
+ // #i62663# synchronize possible asynchronouse printer detection now
+ rManager.checkPrintersChanged( true );
+ }
+ ::std::vector< OUString > aPrinters;
+ rManager.listPrinters( aPrinters );
+
+ for (auto const& printer : aPrinters)
+ {
+ const PrinterInfo& rInfo( rManager.getPrinterInfo(printer) );
+ // create new entry
+ std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo);
+ pInfo->maPrinterName = printer;
+ pInfo->maDriver = rInfo.m_aDriverName;
+ pInfo->maLocation = rInfo.m_aLocation;
+ pInfo->maComment = rInfo.m_aComment;
+
+ OUString sPdfDir;
+ if (getPdfDir(rInfo, sPdfDir))
+ pInfo->maLocation = sPdfDir;
+
+ pList->Add( std::move(pInfo) );
+ }
+}
+
+void SalGenericInstance::GetPrinterQueueState( SalPrinterQueueInfo* )
+{
+ mbPrinterInit = true;
+}
+
+OUString SalGenericInstance::GetDefaultPrinter()
+{
+ mbPrinterInit = true;
+ PrinterInfoManager& rManager( PrinterInfoManager::get() );
+ return rManager.getDefaultPrinter();
+}
+
+PspSalInfoPrinter::PspSalInfoPrinter()
+{
+}
+
+PspSalInfoPrinter::~PspSalInfoPrinter()
+{
+}
+
+void PspSalInfoPrinter::InitPaperFormats( const ImplJobSetup* )
+{
+ m_aPaperFormats.clear();
+ m_bPapersInit = true;
+
+ if( !m_aJobData.m_pParser )
+ return;
+
+ const PPDKey* pKey = m_aJobData.m_pParser->getKey( "PageSize" );
+ if( pKey )
+ {
+ int nValues = pKey->countValues();
+ for( int i = 0; i < nValues; i++ )
+ {
+ const PPDValue* pValue = pKey->getValue( i );
+ int nWidth = 0, nHeight = 0;
+ m_aJobData.m_pParser->getPaperDimension( pValue->m_aOption, nWidth, nHeight );
+ PaperInfo aInfo(PtTo10Mu( nWidth ), PtTo10Mu( nHeight ));
+ m_aPaperFormats.push_back( aInfo );
+ }
+ }
+}
+
+int PspSalInfoPrinter::GetLandscapeAngle( const ImplJobSetup* )
+{
+ return 900;
+}
+
+SalGraphics* PspSalInfoPrinter::AcquireGraphics()
+{
+ // return a valid pointer only once
+ // the reasoning behind this is that we could have different
+ // SalGraphics that can run in multiple threads
+ // (future plans)
+ SalGraphics* pRet = nullptr;
+ if( ! m_pGraphics )
+ {
+ m_pGraphics = GetGenericInstance()->CreatePrintGraphics();
+ m_pGraphics->Init(&m_aJobData, &m_aPrinterGfx);
+ pRet = m_pGraphics.get();
+ }
+ return pRet;
+}
+
+void PspSalInfoPrinter::ReleaseGraphics( SalGraphics* pGraphics )
+{
+ if( m_pGraphics.get() == pGraphics )
+ {
+ m_pGraphics.reset();
+ }
+}
+
+bool PspSalInfoPrinter::Setup( weld::Window* pFrame, ImplJobSetup* pJobSetup )
+{
+ if( ! pFrame || ! pJobSetup )
+ return false;
+
+ PrinterInfoManager& rManager = PrinterInfoManager::get();
+
+ PrinterInfo aInfo( rManager.getPrinterInfo( pJobSetup->GetPrinterName() ) );
+ if ( pJobSetup->GetDriverData() )
+ {
+ SetData( JobSetFlags::ALL, pJobSetup );
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aInfo );
+ }
+ aInfo.m_bPapersizeFromSetup = pJobSetup->GetPapersizeFromSetup();
+ aInfo.meSetupMode = pJobSetup->GetPrinterSetupMode();
+
+ if (SetupPrinterDriver(pFrame, aInfo))
+ {
+ aInfo.resolveDefaultBackend();
+ std::free( const_cast<sal_uInt8*>(pJobSetup->GetDriverData()) );
+ pJobSetup->SetDriverData( nullptr );
+
+ sal_uInt32 nBytes;
+ void* pBuffer = nullptr;
+ aInfo.getStreamBuffer( pBuffer, nBytes );
+ pJobSetup->SetDriverDataLen( nBytes );
+ pJobSetup->SetDriverData( static_cast<sal_uInt8*>(pBuffer) );
+
+ // copy everything to job setup
+ copyJobDataToJobSetup( pJobSetup, aInfo );
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), m_aJobData );
+ return true;
+ }
+ return false;
+}
+
+// This function gets the driver data and puts it into pJobSetup
+// If pJobSetup->GetDriverData() is NOT NULL, then the independent
+// data should be merged into the driver data
+// If pJobSetup->GetDriverData() IS NULL, then the driver defaults
+// should be merged into the independent data
+bool PspSalInfoPrinter::SetPrinterData( ImplJobSetup* pJobSetup )
+{
+ if( pJobSetup->GetDriverData() )
+ return SetData( JobSetFlags::ALL, pJobSetup );
+
+ copyJobDataToJobSetup( pJobSetup, m_aJobData );
+
+ return true;
+}
+
+// This function merges the independent driver data
+// and sets the new independent data in pJobSetup
+// Only the data must be changed, where the bit
+// in nGetDataFlags is set
+bool PspSalInfoPrinter::SetData(
+ JobSetFlags nSetDataFlags,
+ ImplJobSetup* pJobSetup )
+{
+ JobData aData;
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+
+ if( aData.m_pParser )
+ {
+ const PPDKey* pKey;
+ const PPDValue* pValue;
+
+ // merge papersize if necessary
+ if( nSetDataFlags & JobSetFlags::PAPERSIZE )
+ {
+ OUString aPaper;
+
+ if( pJobSetup->GetPaperFormat() == PAPER_USER )
+ aPaper = aData.m_pParser->matchPaper(
+ TenMuToPt( pJobSetup->GetPaperWidth() ),
+ TenMuToPt( pJobSetup->GetPaperHeight() ) );
+ else
+ aPaper = OStringToOUString(PaperInfo::toPSName(pJobSetup->GetPaperFormat()), RTL_TEXTENCODING_ISO_8859_1);
+
+ pKey = aData.m_pParser->getKey( "PageSize" );
+ pValue = pKey ? pKey->getValueCaseInsensitive( aPaper ) : nullptr;
+
+ // some PPD files do not specify the standard paper names (e.g. C5 instead of EnvC5)
+ // try to find the correct paper anyway using the size
+ if( pKey && ! pValue && pJobSetup->GetPaperFormat() != PAPER_USER )
+ {
+ PaperInfo aInfo( pJobSetup->GetPaperFormat() );
+ aPaper = aData.m_pParser->matchPaper(
+ TenMuToPt( aInfo.getWidth() ),
+ TenMuToPt( aInfo.getHeight() ) );
+ pValue = pKey->getValueCaseInsensitive( aPaper );
+ }
+
+ if( ! ( pKey && pValue && aData.m_aContext.setValue( pKey, pValue ) == pValue ) )
+ return false;
+ }
+
+ // merge paperbin if necessary
+ if( nSetDataFlags & JobSetFlags::PAPERBIN )
+ {
+ pKey = aData.m_pParser->getKey( "InputSlot" );
+ if( pKey )
+ {
+ int nPaperBin = pJobSetup->GetPaperBin();
+ if( nPaperBin >= pKey->countValues() )
+ pValue = pKey->getDefaultValue();
+ else
+ pValue = pKey->getValue( pJobSetup->GetPaperBin() );
+
+ // may fail due to constraints;
+ // real paper bin is copied back to jobsetup in that case
+ aData.m_aContext.setValue( pKey, pValue );
+ }
+ // if printer has no InputSlot key simply ignore this setting
+ // (e.g. SGENPRT has no InputSlot)
+ }
+
+ // merge orientation if necessary
+ if( nSetDataFlags & JobSetFlags::ORIENTATION )
+ aData.m_eOrientation = pJobSetup->GetOrientation() == Orientation::Landscape ? orientation::Landscape : orientation::Portrait;
+
+ // merge duplex if necessary
+ if( nSetDataFlags & JobSetFlags::DUPLEXMODE )
+ {
+ pKey = aData.m_pParser->getKey( "Duplex" );
+ if( pKey )
+ {
+ pValue = nullptr;
+ switch( pJobSetup->GetDuplexMode() )
+ {
+ case DuplexMode::Off:
+ pValue = pKey->getValue( "None" );
+ if( pValue == nullptr )
+ pValue = pKey->getValue( "SimplexNoTumble" );
+ break;
+ case DuplexMode::ShortEdge:
+ pValue = pKey->getValue( "DuplexTumble" );
+ break;
+ case DuplexMode::LongEdge:
+ pValue = pKey->getValue( "DuplexNoTumble" );
+ break;
+ case DuplexMode::Unknown:
+ default:
+ pValue = nullptr;
+ break;
+ }
+ if( ! pValue )
+ pValue = pKey->getDefaultValue();
+ aData.m_aContext.setValue( pKey, pValue );
+ }
+ }
+ aData.m_bPapersizeFromSetup = pJobSetup->GetPapersizeFromSetup();
+
+ m_aJobData = aData;
+ copyJobDataToJobSetup( pJobSetup, aData );
+ return true;
+ }
+
+ return false;
+}
+
+void PspSalInfoPrinter::GetPageInfo(
+ const ImplJobSetup* pJobSetup,
+ tools::Long& rOutWidth, tools::Long& rOutHeight,
+ Point& rPageOffset,
+ Size& rPaperSize )
+{
+ if( ! pJobSetup )
+ return;
+
+ JobData aData;
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+
+ // get the selected page size
+ if( !aData.m_pParser )
+ return;
+
+
+ OUString aPaper;
+ int width, height;
+ int left = 0, top = 0, right = 0, bottom = 0;
+ int nDPI = aData.m_aContext.getRenderResolution();
+
+ if( aData.m_eOrientation == psp::orientation::Portrait )
+ {
+ aData.m_aContext.getPageSize( aPaper, width, height );
+ aData.m_pParser->getMargins( aPaper, left, right, top, bottom );
+ }
+ else
+ {
+ aData.m_aContext.getPageSize( aPaper, height, width );
+ aData.m_pParser->getMargins( aPaper, top, bottom, right, left );
+ }
+
+ rPaperSize.setWidth( width * nDPI / 72 );
+ rPaperSize.setHeight( height * nDPI / 72 );
+ rPageOffset.setX( left * nDPI / 72 );
+ rPageOffset.setY( top * nDPI / 72 );
+ rOutWidth = ( width - left - right ) * nDPI / 72;
+ rOutHeight = ( height - top - bottom ) * nDPI / 72;
+
+}
+
+sal_uInt16 PspSalInfoPrinter::GetPaperBinCount( const ImplJobSetup* pJobSetup )
+{
+ if( ! pJobSetup )
+ return 0;
+
+ JobData aData;
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+
+ const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey( "InputSlot" ): nullptr;
+ return pKey ? pKey->countValues() : 0;
+}
+
+OUString PspSalInfoPrinter::GetPaperBinName( const ImplJobSetup* pJobSetup, sal_uInt16 nPaperBin )
+{
+ JobData aData;
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+
+ if( aData.m_pParser )
+ {
+ const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey( "InputSlot" ): nullptr;
+ if( ! pKey || nPaperBin >= o3tl::make_unsigned(pKey->countValues()) )
+ return aData.m_pParser->getDefaultInputSlot();
+ const PPDValue* pValue = pKey->getValue( nPaperBin );
+ if( pValue )
+ return aData.m_pParser->translateOption( pKey->getKey(), pValue->m_aOption );
+ }
+
+ return OUString();
+}
+
+sal_uInt32 PspSalInfoPrinter::GetCapabilities( const ImplJobSetup* pJobSetup, PrinterCapType nType )
+{
+ switch( nType )
+ {
+ case PrinterCapType::SupportDialog:
+ return 1;
+ case PrinterCapType::Copies:
+ return 0xffff;
+ case PrinterCapType::CollateCopies:
+ {
+ // PPDs don't mention the number of possible collated copies.
+ // so let's guess as many as we want ?
+ return 0xffff;
+ }
+ case PrinterCapType::SetOrientation:
+ return 1;
+ case PrinterCapType::SetPaperSize:
+ return 1;
+ case PrinterCapType::SetPaper:
+ return 0;
+ case PrinterCapType::Fax:
+ {
+ // see if the PPD contains the fax4CUPS "Dial" option and that it's not set
+ // to "manually"
+ JobData aData = PrinterInfoManager::get().getPrinterInfo(pJobSetup->GetPrinterName());
+ if( pJobSetup->GetDriverData() )
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+ const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey("Dial") : nullptr;
+ const PPDValue* pValue = pKey ? aData.m_aContext.getValue(pKey) : nullptr;
+ if (pValue && !pValue->m_aOption.equalsIgnoreAsciiCase("Manually"))
+ return 1;
+ return 0;
+ }
+
+ case PrinterCapType::PDF:
+ if( PrinterInfoManager::get().checkFeatureToken( pJobSetup->GetPrinterName(), "pdf" ) )
+ return 1;
+ else
+ {
+ // see if the PPD contains a value to set PDF device
+ JobData aData = PrinterInfoManager::get().getPrinterInfo( pJobSetup->GetPrinterName() );
+ if( pJobSetup->GetDriverData() )
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+ return aData.m_nPDFDevice > 0 ? 1 : 0;
+ }
+ case PrinterCapType::ExternalDialog:
+ return PrinterInfoManager::get().checkFeatureToken( pJobSetup->GetPrinterName(), "external_dialog" ) ? 1 : 0;
+ case PrinterCapType::UsePullModel:
+ {
+ // see if the PPD contains a value to set PDF device
+ JobData aData = PrinterInfoManager::get().getPrinterInfo( pJobSetup->GetPrinterName() );
+ if( pJobSetup->GetDriverData() )
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+ return aData.m_nPDFDevice > 0 ? 1 : 0;
+ }
+ default: break;
+ }
+ return 0;
+}
+
+/*
+ * SalPrinter
+ */
+PspSalPrinter::PspSalPrinter( SalInfoPrinter* pInfoPrinter )
+ : m_pInfoPrinter( pInfoPrinter )
+ , m_nCopies( 1 )
+ , m_bCollate( false )
+ , m_bPdf( false )
+ , m_bIsPDFWriterJob( false )
+{
+}
+
+PspSalPrinter::~PspSalPrinter()
+{
+}
+
+static OUString getTmpName()
+{
+ OUString aTmp, aSys;
+ osl_createTempFile( nullptr, nullptr, &aTmp.pData );
+ osl_getSystemPathFromFileURL( aTmp.pData, &aSys.pData );
+
+ return aSys;
+}
+
+bool PspSalPrinter::StartJob(
+ const OUString* pFileName,
+ const OUString& rJobName,
+ const OUString& rAppName,
+ sal_uInt32 nCopies,
+ bool bCollate,
+ bool bDirect,
+ ImplJobSetup* pJobSetup )
+{
+ SAL_INFO( "vcl.unx.print", "PspSalPrinter::StartJob");
+ GetSalInstance()->jobStartedPrinterUpdate();
+ m_bPdf = false;
+ if (pFileName)
+ m_aFileName = *pFileName;
+ else
+ m_aFileName.clear();
+ m_aTmpFile.clear();
+ m_nCopies = nCopies;
+ m_bCollate = bCollate;
+
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), m_aJobData );
+ if( m_nCopies > 1 )
+ {
+ // in case user did not do anything (m_nCopies=1)
+ // take the default from jobsetup
+ m_aJobData.m_nCopies = m_nCopies;
+ m_aJobData.setCollate( bCollate );
+ }
+
+ int nMode = 0;
+ // check whether this printer is configured as fax
+ const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( m_aJobData.m_aPrinterName ) );
+ OUString sPdfDir;
+ if (getPdfDir(rInfo, sPdfDir))
+ {
+ m_bPdf = true;
+ m_aTmpFile = getTmpName();
+ nMode = S_IRUSR | S_IWUSR;
+
+ if( m_aFileName.isEmpty() )
+ m_aFileName = sPdfDir + "/" + rJobName + ".pdf";
+ }
+ m_aPrinterGfx.Init( m_aJobData );
+
+ return m_aPrintJob.StartJob( ! m_aTmpFile.isEmpty() ? m_aTmpFile : m_aFileName, nMode, rJobName, rAppName, m_aJobData, &m_aPrinterGfx, bDirect );
+}
+
+bool PspSalPrinter::EndJob()
+{
+ bool bSuccess = false;
+ if( m_bIsPDFWriterJob )
+ bSuccess = true;
+ else
+ {
+ bSuccess = m_aPrintJob.EndJob();
+ SAL_INFO( "vcl.unx.print", "PspSalPrinter::EndJob " << bSuccess);
+
+ if( bSuccess && m_bPdf )
+ {
+ const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( m_aJobData.m_aPrinterName ) );
+ bSuccess = createPdf( m_aFileName, m_aTmpFile, rInfo.m_aCommand );
+ }
+ }
+ GetSalInstance()->jobEndedPrinterUpdate();
+ return bSuccess;
+}
+
+SalGraphics* PspSalPrinter::StartPage( ImplJobSetup* pJobSetup, bool )
+{
+ SAL_INFO( "vcl.unx.print", "PspSalPrinter::StartPage");
+
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), m_aJobData );
+ m_xGraphics = GetGenericInstance()->CreatePrintGraphics();
+ m_xGraphics->Init(&m_aJobData, &m_aPrinterGfx);
+
+ if( m_nCopies > 1 )
+ {
+ // in case user did not do anything (m_nCopies=1)
+ // take the default from jobsetup
+ m_aJobData.m_nCopies = m_nCopies;
+ m_aJobData.setCollate( m_nCopies > 1 && m_bCollate );
+ }
+
+ m_aPrintJob.StartPage( m_aJobData );
+ m_aPrinterGfx.Init( m_aPrintJob );
+
+ return m_xGraphics.get();
+}
+
+void PspSalPrinter::EndPage()
+{
+ m_aPrintJob.EndPage();
+ m_aPrinterGfx.Clear();
+ SAL_INFO( "vcl.unx.print", "PspSalPrinter::EndPage");
+}
+
+namespace {
+
+struct PDFNewJobParameters
+{
+ Size maPageSize;
+ sal_uInt16 mnPaperBin;
+
+ PDFNewJobParameters( const Size& i_rSize = Size(),
+ sal_uInt16 i_nPaperBin = 0xffff )
+ : maPageSize( i_rSize ), mnPaperBin( i_nPaperBin ) {}
+
+ bool operator==(const PDFNewJobParameters& rComp ) const
+ {
+ const tools::Long nRotatedWidth = rComp.maPageSize.Height();
+ const tools::Long nRotatedHeight = rComp.maPageSize.Width();
+ Size aCompLSSize(nRotatedWidth, nRotatedHeight);
+ return
+ (maPageSize == rComp.maPageSize || maPageSize == aCompLSSize)
+ && mnPaperBin == rComp.mnPaperBin
+ ;
+ }
+
+ bool operator!=(const PDFNewJobParameters& rComp) const
+ {
+ return ! operator==(rComp);
+ }
+};
+
+struct PDFPrintFile
+{
+ OUString maTmpURL;
+ PDFNewJobParameters maParameters;
+
+ PDFPrintFile( const OUString& i_rURL, const PDFNewJobParameters& i_rNewParameters )
+ : maTmpURL( i_rURL )
+ , maParameters( i_rNewParameters ) {}
+};
+
+}
+
+bool PspSalPrinter::StartJob( const OUString* i_pFileName, const OUString& i_rJobName, const OUString& i_rAppName,
+ ImplJobSetup* i_pSetupData, vcl::PrinterController& i_rController )
+{
+ SAL_INFO( "vcl.unx.print", "StartJob with controller: pFilename = " << (i_pFileName ? *i_pFileName : "<nil>") );
+ // mark for endjob
+ m_bIsPDFWriterJob = true;
+ // reset IsLastPage
+ i_rController.setLastPage( false );
+ // is this a fax device
+ bool bFax = m_pInfoPrinter->GetCapabilities(i_pSetupData, PrinterCapType::Fax) == 1;
+
+ // update job data
+ if( i_pSetupData )
+ JobData::constructFromStreamBuffer( i_pSetupData->GetDriverData(), i_pSetupData->GetDriverDataLen(), m_aJobData );
+
+ OSL_ASSERT( m_aJobData.m_nPDFDevice > 0 );
+ m_aJobData.m_nPDFDevice = 1;
+
+ // possibly create one job for collated output
+ int nCopies = i_rController.getPrinter()->GetCopyCount();
+ bool bCollate = i_rController.getPrinter()->IsCollateCopy();
+ bool bSinglePrintJobs = i_rController.getPrinter()->IsSinglePrintJobs();
+
+ // notify start of real print job
+ i_rController.jobStarted();
+
+ // setup PDFWriter context
+ vcl::PDFWriter::PDFWriterContext aContext;
+ aContext.Version = vcl::PDFWriter::PDFVersion::PDF_1_4;
+ aContext.Tagged = false;
+ aContext.DocumentLocale = Application::GetSettings().GetLanguageTag().getLocale();
+ aContext.ColorMode = i_rController.getPrinter()->GetPrinterOptions().IsConvertToGreyscales()
+ ? vcl::PDFWriter::DrawGreyscale : vcl::PDFWriter::DrawColor;
+
+ // prepare doc info
+ aContext.DocumentInfo.Title = i_rJobName;
+ aContext.DocumentInfo.Creator = i_rAppName;
+ aContext.DocumentInfo.Producer = i_rAppName;
+
+ // define how we handle metafiles in PDFWriter
+ vcl::PDFWriter::PlayMetafileContext aMtfContext;
+ aMtfContext.m_bOnlyLosslessCompression = true;
+
+ std::shared_ptr<vcl::PDFWriter> xWriter;
+ std::vector< PDFPrintFile > aPDFFiles;
+ VclPtr<Printer> xPrinter( i_rController.getPrinter() );
+ int nAllPages = i_rController.getFilteredPageCount();
+ i_rController.createProgressDialog();
+ bool bAborted = false;
+ PDFNewJobParameters aLastParm;
+
+ aContext.DPIx = xPrinter->GetDPIX();
+ aContext.DPIy = xPrinter->GetDPIY();
+ for( int nPage = 0; nPage < nAllPages && ! bAborted; nPage++ )
+ {
+ if( nPage == nAllPages-1 )
+ i_rController.setLastPage( true );
+
+ // get the page's metafile
+ GDIMetaFile aPageFile;
+ vcl::PrinterController::PageSize aPageSize = i_rController.getFilteredPageFile( nPage, aPageFile );
+ if( i_rController.isProgressCanceled() )
+ {
+ bAborted = true;
+ if( nPage != nAllPages-1 )
+ {
+ i_rController.createProgressDialog();
+ i_rController.setLastPage( true );
+ i_rController.getFilteredPageFile( nPage, aPageFile );
+ }
+ }
+ else
+ {
+ xPrinter->SetMapMode( MapMode( MapUnit::Map100thMM ) );
+ xPrinter->SetPaperSizeUser( aPageSize.aSize );
+ PDFNewJobParameters aNewParm(xPrinter->GetPaperSize(), xPrinter->GetPaperBin());
+
+ // create PDF writer on demand
+ // either on first page
+ // or on paper format change - cups does not support multiple paper formats per job (yet?)
+ // so we need to start a new job to get a new paper format from the printer
+ // orientation switches (that is switch of height and width) is handled transparently by CUPS
+ if( ! xWriter ||
+ (aNewParm != aLastParm && ! i_pFileName ) )
+ {
+ if( xWriter )
+ {
+ xWriter->Emit();
+ }
+ // produce PDF file
+ OUString aPDFUrl;
+ if( i_pFileName )
+ aPDFUrl = *i_pFileName;
+ else
+ osl_createTempFile( nullptr, nullptr, &aPDFUrl.pData );
+ // normalize to file URL
+ if( !comphelper::isFileUrl(aPDFUrl) )
+ {
+ // this is not a file URL, but it should
+ // form it into an osl friendly file URL
+ OUString aTmp;
+ osl_getFileURLFromSystemPath( aPDFUrl.pData, &aTmp.pData );
+ aPDFUrl = aTmp;
+ }
+ // save current file and paper format
+ aLastParm = aNewParm;
+ aPDFFiles.emplace_back( aPDFUrl, aNewParm );
+ // update context
+ aContext.URL = aPDFUrl;
+
+ // create and initialize PDFWriter
+ xWriter = std::make_shared<vcl::PDFWriter>( aContext, uno::Reference< beans::XMaterialHolder >() );
+ }
+
+ xWriter->NewPage( TenMuToPt( aNewParm.maPageSize.Width() ),
+ TenMuToPt( aNewParm.maPageSize.Height() ),
+ vcl::PDFWriter::Orientation::Portrait );
+
+ xWriter->PlayMetafile( aPageFile, aMtfContext );
+ }
+ }
+
+ // emit the last file
+ if( xWriter )
+ xWriter->Emit();
+
+ // handle collate, copy count and multiple jobs correctly
+ int nOuterJobs = 1;
+ if( bSinglePrintJobs )
+ {
+ nOuterJobs = nCopies;
+ m_aJobData.m_nCopies = 1;
+ }
+ else
+ {
+ if( bCollate )
+ {
+ if (aPDFFiles.size() == 1 && xPrinter->HasSupport(PrinterSupport::CollateCopy))
+ {
+ m_aJobData.setCollate( true );
+ m_aJobData.m_nCopies = nCopies;
+ }
+ else
+ {
+ nOuterJobs = nCopies;
+ m_aJobData.m_nCopies = 1;
+ }
+ }
+ else
+ {
+ m_aJobData.setCollate( false );
+ m_aJobData.m_nCopies = nCopies;
+ }
+ }
+
+ std::vector<OUString> aFaxNumbers;
+
+ // check for fax numbers
+ if (!bAborted && bFax)
+ {
+ aFaxNumbers = getFaxNumbers();
+ bAborted = aFaxNumbers.empty();
+ }
+
+ bool bSuccess(true);
+ // spool files
+ if( ! i_pFileName && ! bAborted )
+ {
+ do
+ {
+ OUString sFaxNumber;
+ if (!aFaxNumbers.empty())
+ {
+ sFaxNumber = aFaxNumbers.back();
+ aFaxNumbers.pop_back();
+ }
+
+ bool bFirstJob = true;
+ for( int nCurJob = 0; nCurJob < nOuterJobs; nCurJob++ )
+ {
+ for( size_t i = 0; i < aPDFFiles.size(); i++ )
+ {
+ oslFileHandle pFile = nullptr;
+ osl_openFile( aPDFFiles[i].maTmpURL.pData, &pFile, osl_File_OpenFlag_Read );
+ if (pFile && (osl_setFilePos(pFile, osl_Pos_Absolut, 0) == osl_File_E_None))
+ {
+ std::vector< char > buffer( 0x10000, 0 );
+ // update job data with current page size
+ Size aPageSize( aPDFFiles[i].maParameters.maPageSize );
+ m_aJobData.setPaper( TenMuToPt( aPageSize.Width() ), TenMuToPt( aPageSize.Height() ) );
+ // update job data with current paperbin
+ m_aJobData.setPaperBin( aPDFFiles[i].maParameters.mnPaperBin );
+
+ // spool current file
+ FILE* fp = PrinterInfoManager::get().startSpool(xPrinter->GetName(), i_rController.isDirectPrint());
+ if( fp )
+ {
+ sal_uInt64 nBytesRead = 0;
+ do
+ {
+ osl_readFile( pFile, buffer.data(), buffer.size(), &nBytesRead );
+ if( nBytesRead > 0 )
+ {
+ size_t nBytesWritten = fwrite(buffer.data(), 1, nBytesRead, fp);
+ OSL_ENSURE(nBytesRead == nBytesWritten, "short write");
+ if (nBytesRead != nBytesWritten)
+ break;
+ }
+ } while( nBytesRead == buffer.size() );
+ OUStringBuffer aBuf( i_rJobName.getLength() + 8 );
+ aBuf.append( i_rJobName );
+ if( i > 0 || nCurJob > 0 )
+ {
+ aBuf.append( ' ' );
+ aBuf.append( sal_Int32( i + nCurJob * aPDFFiles.size() ) );
+ }
+ bSuccess &=
+ PrinterInfoManager::get().endSpool(xPrinter->GetName(), aBuf.makeStringAndClear(), fp, m_aJobData, bFirstJob, sFaxNumber);
+ bFirstJob = false;
+ }
+ }
+ osl_closeFile( pFile );
+ }
+ }
+ }
+ while (!aFaxNumbers.empty());
+ }
+
+ // job has been spooled
+ i_rController.setJobState( bAborted
+ ? view::PrintableState_JOB_ABORTED
+ : (bSuccess ? view::PrintableState_JOB_SPOOLED
+ : view::PrintableState_JOB_SPOOLING_FAILED));
+
+ // clean up the temporary PDF files
+ if( ! i_pFileName || bAborted )
+ {
+ for(PDFPrintFile & rPDFFile : aPDFFiles)
+ {
+ osl_removeFile( rPDFFile.maTmpURL.pData );
+ SAL_INFO( "vcl.unx.print", "removed print PDF file " << rPDFFile.maTmpURL );
+ }
+ }
+
+ return true;
+}
+
+namespace {
+
+class PrinterUpdate
+{
+ static Idle* pPrinterUpdateIdle;
+ static int nActiveJobs;
+
+ static void doUpdate();
+ DECL_STATIC_LINK( PrinterUpdate, UpdateTimerHdl, Timer*, void );
+public:
+ static void update(SalGenericInstance const &rInstance);
+ static void jobStarted() { nActiveJobs++; }
+ static void jobEnded();
+};
+
+}
+
+Idle* PrinterUpdate::pPrinterUpdateIdle = nullptr;
+int PrinterUpdate::nActiveJobs = 0;
+
+void PrinterUpdate::doUpdate()
+{
+ ::psp::PrinterInfoManager& rManager( ::psp::PrinterInfoManager::get() );
+ SalGenericInstance *pInst = GetGenericInstance();
+ if( pInst && rManager.checkPrintersChanged( false ) )
+ pInst->PostPrintersChanged();
+}
+
+IMPL_STATIC_LINK_NOARG( PrinterUpdate, UpdateTimerHdl, Timer*, void )
+{
+ if( nActiveJobs < 1 )
+ {
+ doUpdate();
+ delete pPrinterUpdateIdle;
+ pPrinterUpdateIdle = nullptr;
+ }
+ else
+ pPrinterUpdateIdle->Start();
+}
+
+void PrinterUpdate::update(SalGenericInstance const &rInstance)
+{
+ if( Application::GetSettings().GetMiscSettings().GetDisablePrinting() )
+ return;
+
+ if( ! rInstance.isPrinterInit() )
+ {
+ // #i45389# start background printer detection
+ psp::PrinterInfoManager::get();
+ return;
+ }
+
+ if( nActiveJobs < 1 )
+ doUpdate();
+ else if( ! pPrinterUpdateIdle )
+ {
+ pPrinterUpdateIdle = new Idle("PrinterUpdateTimer");
+ pPrinterUpdateIdle->SetPriority( TaskPriority::LOWEST );
+ pPrinterUpdateIdle->SetInvokeHandler( LINK( nullptr, PrinterUpdate, UpdateTimerHdl ) );
+ pPrinterUpdateIdle->Start();
+ }
+}
+
+void SalGenericInstance::updatePrinterUpdate()
+{
+ PrinterUpdate::update(*this);
+}
+
+void SalGenericInstance::jobStartedPrinterUpdate()
+{
+ PrinterUpdate::jobStarted();
+}
+
+void PrinterUpdate::jobEnded()
+{
+ nActiveJobs--;
+ if( nActiveJobs < 1 )
+ {
+ if( pPrinterUpdateIdle )
+ {
+ pPrinterUpdateIdle->Stop();
+ delete pPrinterUpdateIdle;
+ pPrinterUpdateIdle = nullptr;
+ doUpdate();
+ }
+ }
+}
+
+void SalGenericInstance::jobEndedPrinterUpdate()
+{
+ PrinterUpdate::jobEnded();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/genpspgraphics.cxx b/vcl/unx/generic/print/genpspgraphics.cxx
new file mode 100644
index 000000000..7c4e14b27
--- /dev/null
+++ b/vcl/unx/generic/print/genpspgraphics.cxx
@@ -0,0 +1,521 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <vector>
+
+#include <sal/types.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <i18nlangtag/mslangid.hxx>
+#include <jobdata.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <config_cairo_canvas.h>
+
+#include <fontsubset.hxx>
+#include <unx/freetype_glyphcache.hxx>
+#include <unx/geninst.h>
+#include <unx/genpspgraphics.h>
+#include <unx/printergfx.hxx>
+#include <langboost.hxx>
+#include <fontinstance.hxx>
+#include <fontattributes.hxx>
+#include <impfontmetricdata.hxx>
+#include <font/FontSelectPattern.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <o3tl/string_view.hxx>
+#include <sallayout.hxx>
+
+using namespace psp;
+
+/*******************************************************
+ * GenPspGraphics
+ *******************************************************/
+
+GenPspGraphics::GenPspGraphics()
+ : m_pJobData( nullptr )
+ , m_pPrinterGfx( nullptr )
+{
+}
+
+void GenPspGraphics::Init(psp::JobData* pJob, psp::PrinterGfx* pGfx)
+{
+ m_pBackend = std::make_unique<GenPspGfxBackend>(pGfx);
+ m_pJobData = pJob;
+ m_pPrinterGfx = pGfx;
+ SetLayout( SalLayoutFlags::NONE );
+}
+
+GenPspGraphics::~GenPspGraphics()
+{
+ ReleaseFonts();
+}
+
+void GenPspGraphics::GetResolution( sal_Int32 &rDPIX, sal_Int32 &rDPIY )
+{
+ if (m_pJobData != nullptr)
+ {
+ int x = m_pJobData->m_aContext.getRenderResolution();
+
+ rDPIX = x;
+ rDPIY = x;
+ }
+}
+
+namespace {
+
+class ImplPspFontData : public FreetypeFontFace
+{
+private:
+ sal_IntPtr mnFontId;
+
+public:
+ explicit ImplPspFontData( const psp::FastPrintFontInfo& );
+ virtual sal_IntPtr GetFontId() const override { return mnFontId; }
+};
+
+}
+
+ImplPspFontData::ImplPspFontData(const psp::FastPrintFontInfo& rInfo)
+: FreetypeFontFace(nullptr, GenPspGraphics::Info2FontAttributes(rInfo)),
+ mnFontId( rInfo.m_nID )
+{}
+
+namespace {
+
+class PspSalLayout : public GenericSalLayout
+{
+public:
+ PspSalLayout(psp::PrinterGfx&, LogicalFontInstance &rFontInstance);
+
+ void InitFont() const final override;
+
+private:
+ ::psp::PrinterGfx& mrPrinterGfx;
+ sal_IntPtr mnFontID;
+ int mnFontHeight;
+ int mnFontWidth;
+ bool mbVertical;
+ bool mbArtItalic;
+ bool mbArtBold;
+};
+
+}
+
+PspSalLayout::PspSalLayout(::psp::PrinterGfx& rGfx, LogicalFontInstance &rFontInstance)
+: GenericSalLayout(rFontInstance)
+, mrPrinterGfx(rGfx)
+{
+ mnFontID = mrPrinterGfx.GetFontID();
+ mnFontHeight = mrPrinterGfx.GetFontHeight();
+ mnFontWidth = mrPrinterGfx.GetFontWidth();
+ mbVertical = mrPrinterGfx.GetFontVertical();
+ mbArtItalic = mrPrinterGfx.GetArtificialItalic();
+ mbArtBold = mrPrinterGfx.GetArtificialBold();
+}
+
+void PspSalLayout::InitFont() const
+{
+ GenericSalLayout::InitFont();
+ mrPrinterGfx.SetFont(mnFontID, mnFontHeight, mnFontWidth,
+ mnOrientation, mbVertical, mbArtItalic, mbArtBold);
+}
+
+void GenPspGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+ const GlyphItem* pGlyph;
+ DevicePoint aPos;
+ int nStart = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ m_pPrinterGfx->DrawGlyph(Point(aPos.getX(), aPos.getY()), *pGlyph);
+}
+
+FontCharMapRef GenPspGraphics::GetFontCharMap() const
+{
+ if (!m_pFreetypeFont[0])
+ return nullptr;
+
+ return m_pFreetypeFont[0]->GetFreetypeFont().GetFontCharMap();
+}
+
+bool GenPspGraphics::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ if (!m_pFreetypeFont[0])
+ return false;
+
+ return m_pFreetypeFont[0]->GetFreetypeFont().GetFontCapabilities(rFontCapabilities);
+}
+
+void GenPspGraphics::SetFont(LogicalFontInstance *pFontInstance, int nFallbackLevel)
+{
+ // release all fonts that are to be overridden
+ for( int i = nFallbackLevel; i < MAX_FALLBACK; ++i )
+ {
+ // old server side font is no longer referenced
+ m_pFreetypeFont[i] = nullptr;
+ }
+
+ // return early if there is no new font
+ if (!pFontInstance)
+ return;
+
+ sal_IntPtr nID = pFontInstance->GetFontFace()->GetFontId();
+
+ const vcl::font::FontSelectPattern& rEntry = pFontInstance->GetFontSelectPattern();
+
+ // determine which font attributes need to be emulated
+ bool bArtItalic = false;
+ bool bArtBold = false;
+ if( rEntry.GetItalic() == ITALIC_OBLIQUE || rEntry.GetItalic() == ITALIC_NORMAL )
+ {
+ FontItalic eItalic = m_pPrinterGfx->GetFontMgr().getFontItalic( nID );
+ if( eItalic != ITALIC_NORMAL && eItalic != ITALIC_OBLIQUE )
+ bArtItalic = true;
+ }
+ FontWeight nWeight = rEntry.GetWeight();
+ FontWeight nRealWeight = m_pPrinterGfx->GetFontMgr().getFontWeight( nID );
+ if( nRealWeight <= WEIGHT_MEDIUM && nWeight > WEIGHT_MEDIUM )
+ {
+ bArtBold = true;
+ }
+
+ // also set the serverside font for layouting
+ // requesting a font provided by builtin rasterizer
+ FreetypeFontInstance* pFreetypeFont = static_cast<FreetypeFontInstance*>(pFontInstance);
+ m_pFreetypeFont[ nFallbackLevel ] = pFreetypeFont;
+
+ // ignore fonts with e.g. corrupted font files
+ if (!m_pFreetypeFont[nFallbackLevel]->GetFreetypeFont().TestFont())
+ m_pFreetypeFont[nFallbackLevel] = nullptr;
+
+ // set the printer font
+ m_pPrinterGfx->SetFont( nID,
+ rEntry.mnHeight,
+ rEntry.mnWidth,
+ rEntry.mnOrientation,
+ rEntry.mbVertical,
+ bArtItalic,
+ bArtBold
+ );
+}
+
+void GenPspGraphics::SetTextColor( Color nColor )
+{
+ psp::PrinterColor aColor (nColor.GetRed(),
+ nColor.GetGreen(),
+ nColor.GetBlue());
+ m_pPrinterGfx->SetTextColor (aColor);
+}
+
+bool GenPspGraphics::AddTempDevFont( vcl::font::PhysicalFontCollection*, const OUString&,const OUString& )
+{
+ return false;
+}
+
+bool GenPspGraphics::AddTempDevFontHelper( vcl::font::PhysicalFontCollection* pFontCollection,
+ std::u16string_view rFileURL,
+ const OUString& rFontName)
+{
+ // inform PSP font manager
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ std::vector<psp::fontID> aFontIds = rMgr.addFontFile( rFileURL );
+ if( aFontIds.empty() )
+ return false;
+
+ FreetypeManager& rFreetypeManager = FreetypeManager::get();
+ for (auto const& elem : aFontIds)
+ {
+ // prepare font data
+ psp::FastPrintFontInfo aInfo;
+ rMgr.getFontFastInfo( elem, aInfo );
+ if (!rFontName.isEmpty())
+ aInfo.m_aFamilyName = rFontName;
+
+ // inform glyph cache of new font
+ FontAttributes aDFA = GenPspGraphics::Info2FontAttributes( aInfo );
+ aDFA.IncreaseQualityBy( 5800 );
+
+ int nFaceNum = rMgr.getFontFaceNumber( aInfo.m_nID );
+ int nVariantNum = rMgr.getFontFaceVariation( aInfo.m_nID );
+
+ const OString& rFileName = rMgr.getFontFileSysPath( aInfo.m_nID );
+ rFreetypeManager.AddFontFile(rFileName, nFaceNum, nVariantNum, aInfo.m_nID, aDFA);
+ }
+
+ // announce new font to device's font list
+ rFreetypeManager.AnnounceFonts(pFontCollection);
+ return true;
+}
+
+void GenPspGraphics::GetDevFontList( vcl::font::PhysicalFontCollection *pFontCollection )
+{
+ ::std::vector< psp::fontID > aList;
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ rMgr.getFontList( aList );
+
+ psp::FastPrintFontInfo aInfo;
+ for (auto const& elem : aList)
+ if (rMgr.getFontFastInfo (elem, aInfo))
+ AnnounceFonts( pFontCollection, aInfo );
+
+ // register platform specific font substitutions if available
+ SalGenericInstance::RegisterFontSubstitutors( pFontCollection );
+}
+
+void GenPspGraphics::ClearDevFontCache()
+{
+ FreetypeManager::get().ClearFontCache();
+}
+
+void GenPspGraphics::GetFontMetric(ImplFontMetricDataRef& rxFontMetric, int nFallbackLevel)
+{
+ if (nFallbackLevel >= MAX_FALLBACK)
+ return;
+
+ if (m_pFreetypeFont[nFallbackLevel])
+ m_pFreetypeFont[nFallbackLevel]->GetFreetypeFont().GetFontMetric(rxFontMetric);
+}
+
+std::unique_ptr<GenericSalLayout> GenPspGraphics::GetTextLayout(int nFallbackLevel)
+{
+ assert(m_pFreetypeFont[nFallbackLevel]);
+ if (!m_pFreetypeFont[nFallbackLevel])
+ return nullptr;
+ return std::make_unique<PspSalLayout>(*m_pPrinterGfx, *m_pFreetypeFont[nFallbackLevel]);
+}
+
+bool GenPspGraphics::CreateFontSubset(
+ const OUString& rToFile,
+ const vcl::font::PhysicalFontFace* pFont,
+ const sal_GlyphId* pGlyphIds,
+ const sal_uInt8* pEncoding,
+ sal_Int32* pWidths,
+ int nGlyphCount,
+ FontSubsetInfo& rInfo
+ )
+{
+ // in this context the pFont->GetFontId() is a valid PSP
+ // font since they are the only ones left after the PDF
+ // export has filtered its list of subsettable fonts (for
+ // which this method was created). The correct way would
+ // be to have the FreetypeManager search for the PhysicalFontFace pFont
+ psp::fontID aFont = pFont->GetFontId();
+
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ bool bSuccess = rMgr.createFontSubset( rInfo,
+ aFont,
+ rToFile,
+ pGlyphIds,
+ pEncoding,
+ pWidths,
+ nGlyphCount );
+ return bSuccess;
+}
+
+void GenPspGraphics::GetGlyphWidths( const vcl::font::PhysicalFontFace* pFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc )
+{
+ // in this context the pFont->GetFontId() is a valid PSP
+ // font since they are the only ones left after the PDF
+ // export has filtered its list of subsettable fonts (for
+ // which this method was created). The correct way would
+ // be to have the FreetypeManager search for the PhysicalFontFace pFont
+ psp::fontID aFont = pFont->GetFontId();
+ GenPspGraphics::DoGetGlyphWidths( aFont, bVertical, rWidths, rUnicodeEnc );
+}
+
+void GenPspGraphics::DoGetGlyphWidths( psp::fontID aFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc )
+{
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ rMgr.getGlyphWidths( aFont, bVertical, rWidths, rUnicodeEnc );
+}
+
+FontAttributes GenPspGraphics::Info2FontAttributes( const psp::FastPrintFontInfo& rInfo )
+{
+ FontAttributes aDFA;
+ aDFA.SetFamilyName( rInfo.m_aFamilyName );
+ aDFA.SetStyleName( rInfo.m_aStyleName );
+ aDFA.SetFamilyType( rInfo.m_eFamilyStyle );
+ aDFA.SetWeight( rInfo.m_eWeight );
+ aDFA.SetItalic( rInfo.m_eItalic );
+ aDFA.SetWidthType( rInfo.m_eWidth );
+ aDFA.SetPitch( rInfo.m_ePitch );
+ aDFA.SetSymbolFlag( rInfo.m_aEncoding == RTL_TEXTENCODING_SYMBOL );
+ aDFA.SetQuality(512);
+
+ // add font family name aliases
+ for (auto const& alias : rInfo.m_aAliases)
+ aDFA.AddMapName(alias);
+
+#if OSL_DEBUG_LEVEL > 2
+ if( aDFA.GetMapNames().getLength() > 0 )
+ {
+ SAL_INFO( "vcl.fonts", "using alias names " << aDFA.GetMapNames() << " for font family " << aDFA.GetFamilyName() );
+ }
+#endif
+
+ return aDFA;
+}
+
+namespace vcl
+{
+ const char* getLangBoost()
+ {
+ const char* pLangBoost;
+ const LanguageType eLang = Application::GetSettings().GetUILanguageTag().getLanguageType();
+ if (eLang == LANGUAGE_JAPANESE)
+ pLangBoost = "jan";
+ else if (MsLangId::isKorean(eLang))
+ pLangBoost = "kor";
+ else if (MsLangId::isSimplifiedChinese(eLang))
+ pLangBoost = "zhs";
+ else if (MsLangId::isTraditionalChinese(eLang))
+ pLangBoost = "zht";
+ else
+ pLangBoost = nullptr;
+ return pLangBoost;
+ }
+}
+
+void GenPspGraphics::AnnounceFonts( vcl::font::PhysicalFontCollection* pFontCollection, const psp::FastPrintFontInfo& aInfo )
+{
+ int nQuality = 0;
+
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ OString aFileName( rMgr.getFontFileSysPath( aInfo.m_nID ) );
+ int nPos = aFileName.lastIndexOf( '_' );
+ if( nPos == -1 || aFileName[nPos+1] == '.' )
+ nQuality += 5;
+ else
+ {
+ static const char* pLangBoost = nullptr;
+ static bool bOnce = true;
+ if( bOnce )
+ {
+ bOnce = false;
+ pLangBoost = vcl::getLangBoost();
+ }
+
+ if( pLangBoost )
+ if( o3tl::equalsIgnoreAsciiCase(aFileName.subView( nPos+1, 3 ), pLangBoost ) )
+ nQuality += 10;
+ }
+
+ rtl::Reference<ImplPspFontData> pFD(new ImplPspFontData( aInfo ));
+ pFD->IncreaseQualityBy( nQuality );
+ pFontCollection->Add( pFD.get() );
+}
+
+SystemGraphicsData GenPspGraphics::GetGraphicsData() const
+{
+ return SystemGraphicsData();
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+bool GenPspGraphics::SupportsCairo() const
+{
+ return false;
+}
+
+cairo::SurfaceSharedPtr GenPspGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& /*rSurface*/) const
+{
+ return cairo::SurfaceSharedPtr();
+}
+
+cairo::SurfaceSharedPtr GenPspGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int /*x*/, int /*y*/, int /*width*/, int /*height*/) const
+{
+ return cairo::SurfaceSharedPtr();
+}
+
+cairo::SurfaceSharedPtr GenPspGraphics::CreateBitmapSurface(const OutputDevice& /*rRefDevice*/, const BitmapSystemData& /*rData*/, const Size& /*rSize*/) const
+{
+ return cairo::SurfaceSharedPtr();
+}
+
+css::uno::Any GenPspGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& /*rSurface*/, const basegfx::B2ISize& /*rSize*/) const
+{
+ return css::uno::Any();
+}
+
+#endif // ENABLE_CAIRO_CANVAS
+
+void GenPspGraphics::DoFreeEmbedFontData( const void* pData, tools::Long nLen )
+{
+ if( pData )
+ munmap( const_cast<void *>(pData), nLen );
+}
+
+const void* GenPspGraphics::DoGetEmbedFontData(psp::fontID aFont, tools::Long* pDataLen)
+{
+
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+
+ OString aSysPath = rMgr.getFontFileSysPath( aFont );
+
+ int fd = open( aSysPath.getStr(), O_RDONLY );
+ if( fd < 0 )
+ return nullptr;
+ struct stat aStat;
+ if( fstat( fd, &aStat ) )
+ {
+ close( fd );
+ return nullptr;
+ }
+ void* pFile = mmap( nullptr, aStat.st_size, PROT_READ, MAP_SHARED, fd, 0 );
+ close( fd );
+ if( pFile == MAP_FAILED )
+ return nullptr;
+ *pDataLen = aStat.st_size;
+
+ return pFile;
+}
+
+void GenPspGraphics::FreeEmbedFontData( const void* pData, tools::Long nLen )
+{
+ DoFreeEmbedFontData( pData, nLen );
+}
+
+const void* GenPspGraphics::GetEmbedFontData(const vcl::font::PhysicalFontFace* pFont, tools::Long* pDataLen)
+{
+ // in this context the pFont->GetFontId() is a valid PSP
+ // font since they are the only ones left after the PDF
+ // export has filtered its list of subsettable fonts (for
+ // which this method was created). The correct way would
+ // be to have the FreetypeManager search for the PhysicalFontFace pFont
+ psp::fontID aFont = pFont->GetFontId();
+ return DoGetEmbedFontData(aFont, pDataLen);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/glyphset.cxx b/vcl/unx/generic/print/glyphset.cxx
new file mode 100644
index 000000000..6b0475a62
--- /dev/null
+++ b/vcl/unx/generic/print/glyphset.cxx
@@ -0,0 +1,301 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "glyphset.hxx"
+
+#include <sft.hxx>
+
+#include <unx/printergfx.hxx>
+#include <fontsubset.hxx>
+#include <unx/fontmanager.hxx>
+
+#include <tools/gen.hxx>
+
+#include <osl/thread.h>
+
+#include <rtl/ustring.hxx>
+#include <rtl/strbuf.hxx>
+
+#include <unotools/tempfile.hxx>
+
+#include <algorithm>
+
+using namespace vcl;
+using namespace psp;
+
+GlyphSet::GlyphSet (sal_Int32 nFontID, bool bVertical)
+ : mnFontID (nFontID),
+ mbVertical (bVertical)
+{
+ PrintFontManager &rMgr = PrintFontManager::get();
+ maBaseName = OUStringToOString (rMgr.getPSName(mnFontID),
+ RTL_TEXTENCODING_ASCII_US);
+}
+
+void
+GlyphSet::GetGlyphID (
+ sal_GlyphId nGlyph,
+ unsigned char* nOutGlyphID,
+ sal_Int32* nOutGlyphSetID
+ )
+{
+ if (!LookupGlyphID(nGlyph, nOutGlyphID, nOutGlyphSetID))
+ AddGlyphID(nGlyph, nOutGlyphID, nOutGlyphSetID);
+}
+
+bool
+GlyphSet::LookupGlyphID (
+ sal_GlyphId nGlyph,
+ unsigned char* nOutGlyphID,
+ sal_Int32* nOutGlyphSetID
+ )
+{
+ sal_Int32 nGlyphSetID = 1;
+
+ // loop through all the font subsets
+ for (auto const& glyph : maGlyphList)
+ {
+ // check every subset if it contains the queried unicode char
+ glyph_map_t::const_iterator aGlyph = glyph.find (nGlyph);
+ if (aGlyph != glyph.end())
+ {
+ // success: found the glyph id, return the mapped glyphid and the glyphsetid
+ *nOutGlyphSetID = nGlyphSetID;
+ *nOutGlyphID = aGlyph->second;
+ return true;
+ }
+ ++nGlyphSetID;
+ }
+
+ *nOutGlyphSetID = -1;
+ *nOutGlyphID = 0;
+ return false;
+}
+
+void
+GlyphSet::AddNotdef (glyph_map_t &rGlyphMap)
+{
+ if (rGlyphMap.empty())
+ rGlyphMap[0] = 0;
+}
+
+void
+GlyphSet::AddGlyphID (
+ sal_GlyphId nGlyph,
+ unsigned char* nOutGlyphID,
+ sal_Int32* nOutGlyphSetID
+ )
+{
+ // create an empty glyphmap that is reserved for unencoded symbol glyphs,
+ // and a second map that takes any other
+ if (maGlyphList.empty())
+ {
+ glyph_map_t aMap, aMapp;
+
+ maGlyphList.push_back (aMap);
+ maGlyphList.push_back (aMapp);
+ }
+ // if the last map is full, create a new one
+ if (maGlyphList.back().size() == 255)
+ {
+ glyph_map_t aMap;
+ maGlyphList.push_back (aMap);
+ }
+
+ glyph_map_t& aGlyphSet = maGlyphList.back();
+ AddNotdef (aGlyphSet);
+
+ int nSize = aGlyphSet.size();
+
+ aGlyphSet [nGlyph] = nSize;
+ *nOutGlyphSetID = maGlyphList.size();
+ *nOutGlyphID = aGlyphSet [nGlyph];
+}
+
+OString
+GlyphSet::GetGlyphSetName (sal_Int32 nGlyphSetID)
+{
+ OStringBuffer aSetName( maBaseName.getLength() + 32 );
+ aSetName.append( maBaseName );
+ aSetName.append( "FID" );
+ aSetName.append( mnFontID );
+ aSetName.append( mbVertical ? "VGSet" : "HGSet" );
+ aSetName.append( nGlyphSetID );
+ return aSetName.makeStringAndClear();
+}
+
+OString
+GlyphSet::GetReencodedFontName (rtl_TextEncoding nEnc, std::string_view rFontName)
+{
+ if ( nEnc == RTL_TEXTENCODING_MS_1252
+ || nEnc == RTL_TEXTENCODING_ISO_8859_1)
+ {
+ return OString::Concat(rFontName) + "-iso1252";
+ }
+ else
+ if (nEnc >= RTL_TEXTENCODING_USER_START && nEnc <= RTL_TEXTENCODING_USER_END)
+ {
+ return OString::Concat(rFontName)
+ + "-enc"
+ + OString::number(nEnc - RTL_TEXTENCODING_USER_START);
+ }
+ else
+ {
+ return OString();
+ }
+}
+
+void GlyphSet::DrawGlyph(PrinterGfx& rGfx,
+ const Point& rPoint,
+ const sal_GlyphId nGlyphId)
+{
+ unsigned char nGlyphID;
+ sal_Int32 nGlyphSetID;
+
+ // convert to font glyph id and font subset
+ GetGlyphID (nGlyphId, &nGlyphID, &nGlyphSetID);
+
+ OString aGlyphSetName = GetGlyphSetName(nGlyphSetID);
+
+ rGfx.PSSetFont (aGlyphSetName, RTL_TEXTENCODING_DONTKNOW);
+ rGfx.PSMoveTo (rPoint);
+ rGfx.PSShowGlyph(nGlyphID);
+}
+
+namespace {
+
+struct EncEntry
+{
+ unsigned char aEnc;
+ tools::Long aGID;
+
+ EncEntry() : aEnc( 0 ), aGID( 0 ) {}
+
+ bool operator<( const EncEntry& rRight ) const
+ { return aEnc < rRight.aEnc; }
+};
+
+}
+
+static void CreatePSUploadableFont( TrueTypeFont* pSrcFont, FILE* pTmpFile,
+ const char* pGlyphSetName, int nGlyphCount,
+ /*const*/ const sal_uInt16* pRequestedGlyphs, /*const*/ const unsigned char* pEncoding,
+ bool bAllowType42 )
+{
+ // match the font-subset to the printer capabilities
+ // TODO: allow CFF for capable printers
+ FontType nTargetMask = FontType::TYPE1_PFA | FontType::TYPE3_FONT;
+ if( bAllowType42 )
+ nTargetMask |= FontType::TYPE42_FONT;
+
+ std::vector< EncEntry > aSorted( nGlyphCount, EncEntry() );
+ for( int i = 0; i < nGlyphCount; i++ )
+ {
+ aSorted[i].aEnc = pEncoding[i];
+ aSorted[i].aGID = pRequestedGlyphs[i];
+ }
+
+ std::stable_sort( aSorted.begin(), aSorted.end() );
+
+ std::vector< unsigned char > aEncoding( nGlyphCount );
+ std::vector< sal_GlyphId > aRequestedGlyphs( nGlyphCount );
+
+ for( int i = 0; i < nGlyphCount; i++ )
+ {
+ aEncoding[i] = aSorted[i].aEnc;
+ aRequestedGlyphs[i] = aSorted[i].aGID;
+ }
+
+ FontSubsetInfo aInfo;
+ aInfo.LoadFont( pSrcFont );
+
+ aInfo.CreateFontSubset( nTargetMask, pTmpFile, pGlyphSetName,
+ aRequestedGlyphs.data(), aEncoding.data(), nGlyphCount );
+}
+
+void
+GlyphSet::PSUploadFont (osl::File& rOutFile, PrinterGfx &rGfx, bool bAllowType42, std::vector< OString >& rSuppliedFonts )
+{
+ TrueTypeFont *pTTFont;
+ OString aTTFileName (rGfx.GetFontMgr().getFontFileSysPath(mnFontID));
+ int nFace = rGfx.GetFontMgr().getFontFaceNumber(mnFontID);
+ SFErrCodes nSuccess = OpenTTFontFile(aTTFileName.getStr(), nFace, &pTTFont);
+ if (nSuccess != SFErrCodes::Ok)
+ return;
+
+ utl::TempFile aTmpFile;
+ aTmpFile.EnableKillingFile();
+ FILE* pTmpFile = fopen(OUStringToOString(aTmpFile.GetFileName(), osl_getThreadTextEncoding()).getStr(), "w+b");
+ if (pTmpFile == nullptr)
+ return;
+
+ // encoding vector maps character encoding to the ordinal number
+ // of the glyph in the output file
+ unsigned char pEncoding[256];
+ sal_uInt16 pTTGlyphMapping[256];
+
+ // loop through all the font glyph subsets
+ sal_Int32 nGlyphSetID = 1;
+ for (auto const& glyph : maGlyphList)
+ {
+ if (glyph.empty())
+ {
+ ++nGlyphSetID;
+ continue;
+ }
+
+ // loop through all the glyphs in the subset
+ sal_Int32 n = 0;
+ for (auto const& elem : glyph)
+ {
+ pTTGlyphMapping [n] = elem.first;
+ pEncoding [n] = elem.second;
+ n++;
+ }
+
+ // create the current subset
+ OString aGlyphSetName = GetGlyphSetName(nGlyphSetID);
+ fprintf( pTmpFile, "%%%%BeginResource: font %s\n", aGlyphSetName.getStr() );
+ CreatePSUploadableFont( pTTFont, pTmpFile, aGlyphSetName.getStr(), glyph.size(),
+ pTTGlyphMapping, pEncoding, bAllowType42 );
+ fprintf( pTmpFile, "%%%%EndResource\n" );
+ rSuppliedFonts.push_back( aGlyphSetName );
+ ++nGlyphSetID;
+ }
+
+ // copy the file into the page header
+ rewind(pTmpFile);
+ fflush(pTmpFile);
+
+ unsigned char pBuffer[0x2000];
+ sal_uInt64 nIn;
+ sal_uInt64 nOut;
+ do
+ {
+ nIn = fread(pBuffer, 1, sizeof(pBuffer), pTmpFile);
+ rOutFile.write (pBuffer, nIn, nOut);
+ }
+ while ((nIn == nOut) && !feof(pTmpFile));
+
+ // cleanup
+ CloseTTFont (pTTFont);
+ fclose (pTmpFile);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/glyphset.hxx b/vcl/unx/generic/print/glyphset.hxx
new file mode 100644
index 000000000..db7fe72ef
--- /dev/null
+++ b/vcl/unx/generic/print/glyphset.hxx
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osl/file.hxx>
+
+#include <rtl/string.hxx>
+
+#include <glyphid.hxx>
+
+#include <string_view>
+#include <vector>
+#include <unordered_map>
+
+class Point;
+
+namespace psp {
+
+class PrinterGfx;
+class PrintFontManager;
+
+class GlyphSet
+{
+private:
+
+ sal_Int32 mnFontID;
+ bool mbVertical;
+ OString maBaseName;
+
+ typedef std::unordered_map< sal_GlyphId, sal_uInt8 > glyph_map_t;
+ std::vector< glyph_map_t > maGlyphList;
+
+ OString GetGlyphSetName (sal_Int32 nGlyphSetID);
+
+ void GetGlyphID (sal_GlyphId nGlyphId,
+ unsigned char* nOutGlyphID, sal_Int32* nOutGlyphSetID);
+ bool LookupGlyphID (sal_GlyphId nGlyphId,
+ unsigned char* nOutGlyphID, sal_Int32* nOutGlyphSetID);
+ void AddGlyphID (sal_GlyphId nGlyphId,
+ unsigned char* nOutGlyphID,
+ sal_Int32* nOutGlyphSetID);
+ static void AddNotdef (glyph_map_t &rGlyphMap);
+
+public:
+
+ GlyphSet (sal_Int32 nFontID, bool bVertical);
+ /* FIXME delete the glyphlist in ~GlyphSet ??? */
+
+ sal_Int32 GetFontID () const { return mnFontID;}
+ static OString
+ GetReencodedFontName (rtl_TextEncoding nEnc,
+ std::string_view rFontName);
+
+ bool IsVertical () const { return mbVertical;}
+
+ void DrawGlyph (PrinterGfx& rGfx,
+ const Point& rPoint,
+ const sal_GlyphId nGlyphId);
+ void PSUploadFont (osl::File& rOutFile, PrinterGfx &rGfx, bool bAsType42, std::vector< OString >& rSuppliedFonts );
+};
+
+} /* namespace psp */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/printerjob.cxx b/vcl/unx/generic/print/printerjob.cxx
new file mode 100644
index 000000000..233bd2195
--- /dev/null
+++ b/vcl/unx/generic/print/printerjob.cxx
@@ -0,0 +1,973 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <stdio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "psputil.hxx"
+
+#include <unx/printerjob.hxx>
+#include <unx/printergfx.hxx>
+#include <ppdparser.hxx>
+#include <strhelper.hxx>
+#include <printerinfomanager.hxx>
+
+#include <rtl/ustring.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <osl/thread.h>
+#include <osl/security.hxx>
+
+#include <algorithm>
+#include <cstddef>
+#include <deque>
+#include <vector>
+
+using namespace psp;
+
+#define nBLOCKSIZE 0x2000
+
+namespace psp
+{
+
+static bool
+AppendPS (FILE* pDst, osl::File* pSrc, unsigned char* pBuffer)
+{
+ assert(pBuffer);
+ if ((pDst == nullptr) || (pSrc == nullptr))
+ return false;
+
+ if (pSrc->setPos(osl_Pos_Absolut, 0) != osl::FileBase::E_None)
+ return false;
+
+ sal_uInt64 nIn = 0;
+ sal_uInt64 nOut = 0;
+ do
+ {
+ pSrc->read (pBuffer, nBLOCKSIZE, nIn);
+ if (nIn > 0)
+ nOut = fwrite (pBuffer, 1, sal::static_int_cast<sal_uInt32>(nIn), pDst);
+ }
+ while ((nIn > 0) && (nIn == nOut));
+
+ return true;
+}
+
+} // namespace psp
+
+/*
+ * private convenience routines for file handling
+ */
+
+std::unique_ptr<osl::File>
+PrinterJob::CreateSpoolFile (std::u16string_view rName, std::u16string_view rExtension) const
+{
+ OUString aFile = OUString::Concat(rName) + rExtension;
+ OUString aFileURL;
+ osl::File::RC nError = osl::File::getFileURLFromSystemPath( aFile, aFileURL );
+ if (nError != osl::File::E_None)
+ return nullptr;
+ aFileURL = maSpoolDirName + "/" + aFileURL;
+
+ std::unique_ptr<osl::File> pFile( new osl::File (aFileURL) );
+ nError = pFile->open (osl_File_OpenFlag_Read | osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
+ if (nError != osl::File::E_None)
+ {
+ return nullptr;
+ }
+
+ osl::File::setAttributes (aFileURL,
+ osl_File_Attribute_OwnWrite | osl_File_Attribute_OwnRead);
+ return pFile;
+}
+
+/*
+ * public methods of PrinterJob: for use in PrinterGfx
+ */
+
+void
+PrinterJob::GetScale (double &rXScale, double &rYScale) const
+{
+ rXScale = mfXScale;
+ rYScale = mfYScale;
+}
+
+sal_uInt16
+PrinterJob::GetDepth () const
+{
+ sal_Int32 nLevel = GetPostscriptLevel();
+ bool bColor = IsColorPrinter ();
+
+ return nLevel > 1 && bColor ? 24 : 8;
+}
+
+sal_uInt16
+PrinterJob::GetPostscriptLevel (const JobData *pJobData) const
+{
+ sal_uInt16 nPSLevel = 2;
+
+ if( pJobData == nullptr )
+ pJobData = &m_aLastJobData;
+
+ if( pJobData->m_nPSLevel )
+ nPSLevel = pJobData->m_nPSLevel;
+ else
+ if( pJobData->m_pParser )
+ nPSLevel = pJobData->m_pParser->getLanguageLevel();
+
+ return nPSLevel;
+}
+
+bool
+PrinterJob::IsColorPrinter () const
+{
+ bool bColor = false;
+
+ if( m_aLastJobData.m_nColorDevice )
+ bColor = m_aLastJobData.m_nColorDevice != -1;
+ else if( m_aLastJobData.m_pParser )
+ bColor = m_aLastJobData.m_pParser->isColorDevice();
+
+ return bColor;
+}
+
+osl::File*
+PrinterJob::GetCurrentPageBody ()
+{
+ return maPageVector.back().get();
+}
+
+/*
+ * public methods of PrinterJob: the actual job / spool handling
+ */
+PrinterJob::PrinterJob()
+ : mnFileMode(0)
+ , m_pGraphics(nullptr)
+ , mnResolution(96)
+ , mnWidthPt(0)
+ , mnHeightPt(0)
+ , mnMaxWidthPt(0)
+ , mnMaxHeightPt(0)
+ , mnLandscapes(0)
+ , mnPortraits(0)
+ , mnLMarginPt(0)
+ , mnRMarginPt(0)
+ , mnTMarginPt(0)
+ , mnBMarginPt(0)
+ , mfXScale(1)
+ , mfYScale(1)
+ , m_bQuickJob(false)
+{
+}
+
+/* remove all our temporary files, uses external program "rm", since
+ osl functionality is inadequate */
+static void
+removeSpoolDir (const OUString& rSpoolDir)
+{
+ OUString aSysPath;
+ if( osl::File::E_None != osl::File::getSystemPathFromFileURL( rSpoolDir, aSysPath ) )
+ {
+ // Conversion did not work, as this is quite a dangerous action,
+ // we should abort here...
+ OSL_FAIL( "psprint: couldn't remove spool directory" );
+ return;
+ }
+ OString aSysPathByte =
+ OUStringToOString (aSysPath, osl_getThreadTextEncoding());
+ if (system (OString("rm -rf " + aSysPathByte).getStr()) == -1)
+ OSL_FAIL( "psprint: couldn't remove spool directory" );
+}
+
+/* creates a spool directory with a "pidgin random" value based on
+ current system time */
+static OUString
+createSpoolDir ()
+{
+ TimeValue aCur;
+ osl_getSystemTime( &aCur );
+ sal_Int32 nRand = aCur.Seconds ^ (aCur.Nanosec/1000);
+
+ OUString aTmpDir;
+ osl_getTempDirURL( &aTmpDir.pData );
+
+ do
+ {
+ OUString aDir = aTmpDir + "/psp" + OUString::number(nRand);
+ if( osl::Directory::create( aDir ) == osl::FileBase::E_None )
+ {
+ osl::File::setAttributes( aDir,
+ osl_File_Attribute_OwnWrite
+ | osl_File_Attribute_OwnRead
+ | osl_File_Attribute_OwnExe );
+ return aDir;
+ }
+ nRand++;
+ } while( nRand );
+ return OUString();
+}
+
+PrinterJob::~PrinterJob ()
+{
+ maPageVector.clear();
+ maHeaderVector.clear();
+
+ // mpJobHeader->remove();
+ mpJobHeader.reset();
+ // mpJobTrailer->remove();
+ mpJobTrailer.reset();
+
+ // XXX should really call osl::remove routines
+ if( !maSpoolDirName.isEmpty() )
+ removeSpoolDir (maSpoolDirName);
+
+ // osl::Directory::remove (maSpoolDirName);
+}
+
+static void WriteLocalTimePS( osl::File *rFile )
+{
+ TimeValue aStartTime, tLocal;
+ oslDateTime date_time;
+ if (osl_getSystemTime( &aStartTime ) &&
+ osl_getLocalTimeFromSystemTime( &aStartTime, &tLocal ) &&
+ osl_getDateTimeFromTimeValue( &tLocal, &date_time ))
+ {
+ char ar[ 256 ];
+ snprintf(
+ ar, sizeof (ar),
+ "%04d-%02d-%02d %02d:%02d:%02d ",
+ date_time.Year, date_time.Month, date_time.Day,
+ date_time.Hours, date_time.Minutes, date_time.Seconds );
+ WritePS( rFile, ar );
+ }
+ else
+ WritePS( rFile, "Unknown-Time" );
+}
+
+static bool isAscii( const OUString& rStr )
+{
+ sal_Int32 nLen = rStr.getLength();
+ for( sal_Int32 i = 0; i < nLen; i++ )
+ if( rStr[i] > 127 )
+ return false;
+ return true;
+}
+
+bool
+PrinterJob::StartJob (
+ const OUString& rFileName,
+ int nMode,
+ const OUString& rJobName,
+ std::u16string_view rAppName,
+ const JobData& rSetupData,
+ PrinterGfx* pGraphics,
+ bool bIsQuickJob
+ )
+{
+ m_bQuickJob = bIsQuickJob;
+ mnMaxWidthPt = mnMaxHeightPt = 0;
+ mnLandscapes = mnPortraits = 0;
+ m_pGraphics = pGraphics;
+ InitPaperSize (rSetupData);
+
+ // create file container for document header and trailer
+ maFileName = rFileName;
+ mnFileMode = nMode;
+ maSpoolDirName = createSpoolDir ();
+ maJobTitle = rJobName;
+
+ OUString aExt(".ps");
+ mpJobHeader = CreateSpoolFile (u"psp_head", aExt);
+ mpJobTrailer = CreateSpoolFile (u"psp_tail", aExt);
+ if( ! (mpJobHeader && mpJobTrailer) ) // existing files are removed in destructor
+ return false;
+
+ // write document header according to Document Structuring Conventions (DSC)
+ WritePS (mpJobHeader.get(),
+ "%!PS-Adobe-3.0\n"
+ "%%BoundingBox: (atend)\n" );
+
+ // Creator (this application)
+ OUString aFilterWS = WhitespaceToSpace( rAppName, false );
+ WritePS (mpJobHeader.get(), "%%Creator: (");
+ WritePS (mpJobHeader.get(), aFilterWS);
+ WritePS (mpJobHeader.get(), ")\n");
+
+ // For (user name)
+ osl::Security aSecurity;
+ OUString aUserName;
+ if( aSecurity.getUserName( aUserName ) )
+ {
+ WritePS (mpJobHeader.get(), "%%For: (");
+ WritePS (mpJobHeader.get(), aUserName);
+ WritePS (mpJobHeader.get(), ")\n");
+ }
+
+ // Creation Date (locale independent local time)
+ WritePS (mpJobHeader.get(), "%%CreationDate: (");
+ WriteLocalTimePS (mpJobHeader.get());
+ WritePS (mpJobHeader.get(), ")\n");
+
+ // Document Title
+ /* #i74335#
+ * The title should be clean ascii; rJobName however may
+ * contain any Unicode character. So implement the following
+ * algorithm:
+ * use rJobName, if it contains only ascii
+ * use the filename, if it contains only ascii
+ * else omit %%Title
+ */
+ aFilterWS = WhitespaceToSpace( rJobName, false );
+ OUString aTitle( aFilterWS );
+ if( ! isAscii( aTitle ) )
+ {
+ aTitle = WhitespaceToSpace( rFileName.subView(rFileName.lastIndexOf('/')+1), false );
+ if( ! isAscii( aTitle ) )
+ aTitle.clear();
+ }
+
+ maJobTitle = aFilterWS;
+ if( !aTitle.isEmpty() )
+ {
+ WritePS (mpJobHeader.get(), "%%Title: (");
+ WritePS (mpJobHeader.get(), aTitle);
+ WritePS (mpJobHeader.get(), ")\n");
+ }
+
+ // Language Level
+ OStringBuffer pLevel;
+ getValueOf(GetPostscriptLevel(&rSetupData), pLevel);
+ pLevel.append('\n');
+ WritePS (mpJobHeader.get(), "%%LanguageLevel: ");
+ WritePS (mpJobHeader.get(), pLevel.makeStringAndClear());
+
+ // Other
+ WritePS (mpJobHeader.get(), "%%DocumentData: Clean7Bit\n");
+ WritePS (mpJobHeader.get(), "%%Pages: (atend)\n");
+ WritePS (mpJobHeader.get(), "%%Orientation: (atend)\n");
+ WritePS (mpJobHeader.get(), "%%PageOrder: Ascend\n");
+ WritePS (mpJobHeader.get(), "%%EndComments\n");
+
+ // write Prolog
+ writeProlog (mpJobHeader.get(), rSetupData);
+
+ // mark last job setup as not set
+ m_aLastJobData.m_pParser = nullptr;
+ m_aLastJobData.m_aContext.setParser( nullptr );
+
+ return true;
+}
+
+bool
+PrinterJob::EndJob()
+{
+ // no pages ? that really means no print job
+ if( maPageVector.empty() )
+ return false;
+
+ // write document setup (done here because it
+ // includes the accumulated fonts
+ if( mpJobHeader )
+ writeSetup( mpJobHeader.get(), m_aDocumentJobData );
+ m_pGraphics->OnEndJob();
+ if( ! (mpJobHeader && mpJobTrailer) )
+ return false;
+
+ // write document trailer according to Document Structuring Conventions (DSC)
+ OStringBuffer aTrailer(512);
+ aTrailer.append( "%%Trailer\n" );
+ aTrailer.append( "%%BoundingBox: 0 0 " );
+ aTrailer.append( static_cast<sal_Int32>(mnMaxWidthPt) );
+ aTrailer.append( " " );
+ aTrailer.append( static_cast<sal_Int32>(mnMaxHeightPt) );
+ if( mnLandscapes > mnPortraits )
+ aTrailer.append("\n%%Orientation: Landscape");
+ else
+ aTrailer.append("\n%%Orientation: Portrait");
+ aTrailer.append( "\n%%Pages: " );
+ aTrailer.append( static_cast<sal_Int32>(maPageVector.size()) );
+ aTrailer.append( "\n%%EOF\n" );
+ WritePS (mpJobTrailer.get(), aTrailer.getStr());
+
+ /*
+ * spool the set of files to their final destination, this is U**X dependent
+ */
+
+ FILE* pDestFILE = nullptr;
+
+ /* create a destination either as file or as a pipe */
+ bool bSpoolToFile = !maFileName.isEmpty();
+ if (bSpoolToFile)
+ {
+ const OString aFileName = OUStringToOString (maFileName,
+ osl_getThreadTextEncoding());
+ if( mnFileMode )
+ {
+ int nFile = open( aFileName.getStr(), O_CREAT | O_EXCL | O_RDWR, mnFileMode );
+ if( nFile != -1 )
+ {
+ pDestFILE = fdopen( nFile, "w" );
+ if( pDestFILE == nullptr )
+ {
+ close( nFile );
+ unlink( aFileName.getStr() );
+ return false;
+ }
+ }
+ else
+ {
+ (void)chmod( aFileName.getStr(), mnFileMode );
+ }
+ }
+ if (pDestFILE == nullptr)
+ pDestFILE = fopen (aFileName.getStr(), "w");
+
+ if (pDestFILE == nullptr)
+ return false;
+ }
+ else
+ {
+ PrinterInfoManager& rPrinterInfoManager = PrinterInfoManager::get ();
+ pDestFILE = rPrinterInfoManager.startSpool( m_aLastJobData.m_aPrinterName, m_bQuickJob );
+ if (pDestFILE == nullptr)
+ return false;
+ }
+
+ /* spool the document parts to the destination */
+
+ unsigned char pBuffer[ nBLOCKSIZE ];
+
+ AppendPS (pDestFILE, mpJobHeader.get(), pBuffer);
+ mpJobHeader->close();
+
+ bool bSuccess = true;
+ std::vector< std::unique_ptr<osl::File> >::iterator pPageBody;
+ std::vector< std::unique_ptr<osl::File> >::iterator pPageHead;
+ for (pPageBody = maPageVector.begin(), pPageHead = maHeaderVector.begin();
+ pPageBody != maPageVector.end() && pPageHead != maHeaderVector.end();
+ ++pPageBody, ++pPageHead)
+ {
+ if( *pPageHead )
+ {
+ osl::File::RC nError = (*pPageHead)->open(osl_File_OpenFlag_Read);
+ if (nError == osl::File::E_None)
+ {
+ AppendPS (pDestFILE, pPageHead->get(), pBuffer);
+ (*pPageHead)->close();
+ }
+ }
+ else
+ bSuccess = false;
+ if( *pPageBody )
+ {
+ osl::File::RC nError = (*pPageBody)->open(osl_File_OpenFlag_Read);
+ if (nError == osl::File::E_None)
+ {
+ AppendPS (pDestFILE, pPageBody->get(), pBuffer);
+ (*pPageBody)->close();
+ }
+ }
+ else
+ bSuccess = false;
+ }
+
+ AppendPS (pDestFILE, mpJobTrailer.get(), pBuffer);
+ mpJobTrailer->close();
+
+ /* well done */
+
+ if (bSpoolToFile)
+ fclose (pDestFILE);
+ else
+ {
+ PrinterInfoManager& rPrinterInfoManager = PrinterInfoManager::get();
+ if (!rPrinterInfoManager.endSpool( m_aLastJobData.m_aPrinterName,
+ maJobTitle, pDestFILE, m_aDocumentJobData, true, OUString()))
+ {
+ bSuccess = false;
+ }
+ }
+
+ return bSuccess;
+}
+
+void
+PrinterJob::InitPaperSize (const JobData& rJobSetup)
+{
+ int nRes = rJobSetup.m_aContext.getRenderResolution ();
+
+ OUString aPaper;
+ int nWidth, nHeight;
+ rJobSetup.m_aContext.getPageSize (aPaper, nWidth, nHeight);
+
+ int nLeft = 0, nRight = 0, nUpper = 0, nLower = 0;
+ const PPDParser* pParser = rJobSetup.m_aContext.getParser();
+ if (pParser != nullptr)
+ pParser->getMargins (aPaper, nLeft, nRight, nUpper, nLower);
+
+ mnResolution = nRes;
+
+ mnWidthPt = nWidth;
+ mnHeightPt = nHeight;
+
+ if( mnWidthPt > mnMaxWidthPt )
+ mnMaxWidthPt = mnWidthPt;
+ if( mnHeightPt > mnMaxHeightPt )
+ mnMaxHeightPt = mnHeightPt;
+
+ mnLMarginPt = nLeft;
+ mnRMarginPt = nRight;
+ mnTMarginPt = nUpper;
+ mnBMarginPt = nLower;
+
+ mfXScale = 72.0 / static_cast<double>(mnResolution);
+ mfYScale = -1.0 * 72.0 / static_cast<double>(mnResolution);
+}
+
+void
+PrinterJob::StartPage (const JobData& rJobSetup)
+{
+ InitPaperSize (rJobSetup);
+
+ OUString aPageNo = OUString::number (static_cast<sal_Int32>(maPageVector.size())+1); // sequential page number must start with 1
+ OUString aExt = aPageNo + ".ps";
+
+ maHeaderVector.push_back( CreateSpoolFile ( u"psp_pghead", aExt) );
+ maPageVector.push_back( CreateSpoolFile ( u"psp_pgbody", aExt) );
+
+ osl::File* pPageHeader = maHeaderVector.back().get();
+ osl::File* pPageBody = maPageVector.back().get();
+
+ if( ! (pPageHeader && pPageBody) )
+ return;
+
+ // write page header according to Document Structuring Conventions (DSC)
+ WritePS (pPageHeader, "%%Page: ");
+ WritePS (pPageHeader, aPageNo);
+ WritePS (pPageHeader, " ");
+ WritePS (pPageHeader, aPageNo);
+ WritePS (pPageHeader, "\n");
+
+ if( rJobSetup.m_eOrientation == orientation::Landscape )
+ {
+ WritePS (pPageHeader, "%%PageOrientation: Landscape\n");
+ mnLandscapes++;
+ }
+ else
+ {
+ WritePS (pPageHeader, "%%PageOrientation: Portrait\n");
+ mnPortraits++;
+ }
+
+ OStringBuffer pBBox;
+
+ psp::appendStr ("%%PageBoundingBox: ", pBBox);
+ psp::getValueOf (mnLMarginPt, pBBox);
+ psp::appendStr (" ", pBBox);
+ psp::getValueOf (mnBMarginPt, pBBox);
+ psp::appendStr (" ", pBBox);
+ psp::getValueOf (mnWidthPt - mnRMarginPt, pBBox);
+ psp::appendStr (" ", pBBox);
+ psp::getValueOf (mnHeightPt - mnTMarginPt, pBBox);
+ psp::appendStr ("\n", pBBox);
+
+ WritePS (pPageHeader, pBBox.makeStringAndClear());
+
+ /* #i7262# #i65491# write setup only before first page
+ * (to %%Begin(End)Setup, instead of %%Begin(End)PageSetup)
+ * don't do this in StartJob since the jobsetup there may be
+ * different.
+ */
+ bool bWriteFeatures = true;
+ if( 1 == maPageVector.size() )
+ {
+ m_aDocumentJobData = rJobSetup;
+ bWriteFeatures = false;
+ }
+
+ if ( writePageSetup( pPageHeader, rJobSetup, bWriteFeatures ) )
+ {
+ m_aLastJobData = rJobSetup;
+ }
+}
+
+void
+PrinterJob::EndPage ()
+{
+ osl::File* pPageHeader = maHeaderVector.back().get();
+ osl::File* pPageBody = maPageVector.back().get();
+
+ if( ! (pPageBody && pPageHeader) )
+ return;
+
+ // copy page to paper and write page trailer according to DSC
+
+ OStringBuffer pTrailer;
+ psp::appendStr ("grestore grestore\n", pTrailer);
+ psp::appendStr ("showpage\n", pTrailer);
+ psp::appendStr ("%%PageTrailer\n\n", pTrailer);
+ WritePS (pPageBody, pTrailer.makeStringAndClear());
+
+ // this page is done for now, close it to avoid having too many open fd's
+
+ pPageHeader->close();
+ pPageBody->close();
+}
+
+namespace {
+
+struct less_ppd_key
+{
+ bool operator()(const PPDKey* left, const PPDKey* right)
+ { return left->getOrderDependency() < right->getOrderDependency(); }
+};
+
+}
+
+static bool writeFeature( osl::File* pFile, const PPDKey* pKey, const PPDValue* pValue, bool bUseIncluseFeature )
+{
+ if( ! pKey || ! pValue )
+ return true;
+
+ OStringBuffer aFeature(256);
+ aFeature.append( "[{\n" );
+ if( bUseIncluseFeature )
+ aFeature.append( "%%IncludeFeature:" );
+ else
+ aFeature.append( "%%BeginFeature:" );
+ aFeature.append( " *" );
+ aFeature.append( OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US ) );
+ aFeature.append( ' ' );
+ aFeature.append( OUStringToOString( pValue->m_aOption, RTL_TEXTENCODING_ASCII_US ) );
+ if( !bUseIncluseFeature )
+ {
+ aFeature.append( '\n' );
+ aFeature.append( OUStringToOString( pValue->m_aValue, RTL_TEXTENCODING_ASCII_US ) );
+ aFeature.append( "\n%%EndFeature" );
+ }
+ aFeature.append( "\n} stopped cleartomark\n" );
+ sal_uInt64 nWritten = 0;
+ return !(pFile->write( aFeature.getStr(), aFeature.getLength(), nWritten )
+ || nWritten != static_cast<sal_uInt64>(aFeature.getLength()));
+}
+
+bool PrinterJob::writeFeatureList( osl::File* pFile, const JobData& rJob, bool bDocumentSetup ) const
+{
+ bool bSuccess = true;
+
+ // emit features ordered to OrderDependency
+ // ignore features that are set to default
+
+ // sanity check
+ if( rJob.m_pParser == rJob.m_aContext.getParser() &&
+ rJob.m_pParser &&
+ ( m_aLastJobData.m_pParser == rJob.m_pParser || m_aLastJobData.m_pParser == nullptr )
+ )
+ {
+ std::size_t i;
+ std::size_t nKeys = rJob.m_aContext.countValuesModified();
+ ::std::vector< const PPDKey* > aKeys( nKeys );
+ for( i = 0; i < nKeys; i++ )
+ aKeys[i] = rJob.m_aContext.getModifiedKey( i );
+ ::std::sort( aKeys.begin(), aKeys.end(), less_ppd_key() );
+
+ for( i = 0; i < nKeys && bSuccess; i++ )
+ {
+ const PPDKey* pKey = aKeys[i];
+ bool bEmit = false;
+ if( bDocumentSetup )
+ {
+ if( pKey->getSetupType() == PPDKey::SetupType::DocumentSetup )
+ bEmit = true;
+ }
+ if( pKey->getSetupType() == PPDKey::SetupType::PageSetup ||
+ pKey->getSetupType() == PPDKey::SetupType::AnySetup )
+ bEmit = true;
+ if( bEmit )
+ {
+ const PPDValue* pValue = rJob.m_aContext.getValue( pKey );
+ if( pValue
+ && pValue->m_eType == eInvocation
+ && ( m_aLastJobData.m_pParser == nullptr
+ || m_aLastJobData.m_aContext.getValue( pKey ) != pValue
+ || bDocumentSetup
+ )
+ )
+ {
+ // try to avoid PS level 2 feature commands if level is set to 1
+ if( GetPostscriptLevel( &rJob ) == 1 )
+ {
+ bool bHavePS2 =
+ ( pValue->m_aValue.indexOf( "<<" ) != -1 )
+ ||
+ ( pValue->m_aValue.indexOf( ">>" ) != -1 );
+ if( bHavePS2 )
+ continue;
+ }
+ bSuccess = writeFeature( pFile, pKey, pValue, PrinterInfoManager::get().getUseIncludeFeature() );
+ }
+ }
+ }
+ }
+ else
+ bSuccess = false;
+
+ return bSuccess;
+}
+
+bool PrinterJob::writePageSetup( osl::File* pFile, const JobData& rJob, bool bWriteFeatures )
+{
+ bool bSuccess = true;
+
+ WritePS (pFile, "%%BeginPageSetup\n%\n");
+ if ( bWriteFeatures )
+ bSuccess = writeFeatureList( pFile, rJob, false );
+ WritePS (pFile, "%%EndPageSetup\n");
+
+ OStringBuffer pTranslate;
+
+ if( rJob.m_eOrientation == orientation::Portrait )
+ {
+ psp::appendStr ("gsave\n[", pTranslate);
+ psp::getValueOfDouble ( pTranslate, mfXScale, 5);
+ psp::appendStr (" 0 0 ", pTranslate);
+ psp::getValueOfDouble ( pTranslate, mfYScale, 5);
+ psp::appendStr (" ", pTranslate);
+ psp::getValueOf (mnRMarginPt, pTranslate);
+ psp::appendStr (" ", pTranslate);
+ psp::getValueOf (mnHeightPt-mnTMarginPt,
+ pTranslate);
+ psp::appendStr ("] concat\ngsave\n",
+ pTranslate);
+ }
+ else
+ {
+ psp::appendStr ("gsave\n", pTranslate);
+ psp::appendStr ("[ 0 ", pTranslate);
+ psp::getValueOfDouble ( pTranslate, -mfYScale, 5);
+ psp::appendStr (" ", pTranslate);
+ psp::getValueOfDouble ( pTranslate, mfXScale, 5);
+ psp::appendStr (" 0 ", pTranslate );
+ psp::getValueOfDouble ( pTranslate, mnLMarginPt, 5 );
+ psp::appendStr (" ", pTranslate);
+ psp::getValueOf (mnBMarginPt, pTranslate );
+ psp::appendStr ("] concat\ngsave\n",
+ pTranslate);
+ }
+
+ WritePS (pFile, pTranslate.makeStringAndClear());
+
+ return bSuccess;
+}
+
+void PrinterJob::writeJobPatch( osl::File* pFile, const JobData& rJobData )
+{
+ if( ! PrinterInfoManager::get().getUseJobPatch() )
+ return;
+
+ const PPDKey* pKey = nullptr;
+
+ if( rJobData.m_pParser )
+ pKey = rJobData.m_pParser->getKey( "JobPatchFile" );
+ if( ! pKey )
+ return;
+
+ // order the patch files
+ // according to PPD spec the JobPatchFile options must be int
+ // and should be emitted in order
+ std::deque< sal_Int32 > patch_order;
+ int nValueCount = pKey->countValues();
+ for( int i = 0; i < nValueCount; i++ )
+ {
+ const PPDValue* pVal = pKey->getValue( i );
+ patch_order.push_back( pVal->m_aOption.toInt32() );
+ if( patch_order.back() == 0 && pVal->m_aOption != "0" )
+ {
+ WritePS( pFile, "% Warning: left out JobPatchFile option \"" );
+ OString aOption = OUStringToOString( pVal->m_aOption, RTL_TEXTENCODING_ASCII_US );
+ WritePS( pFile, aOption.getStr() );
+ WritePS( pFile,
+ "\"\n% as it violates the PPD spec;\n"
+ "% JobPatchFile options need to be numbered for ordering.\n" );
+ }
+ }
+
+ std::sort(patch_order.begin(), patch_order.end());
+ patch_order.erase(std::unique(patch_order.begin(), patch_order.end()), patch_order.end());
+
+ for (auto const& elem : patch_order)
+ {
+ // note: this discards patch files not adhering to the "int" scheme
+ // as there won't be a value for them
+ writeFeature( pFile, pKey, pKey->getValue( OUString::number(elem) ), false );
+ }
+}
+
+void PrinterJob::writeProlog (osl::File* pFile, const JobData& rJobData )
+{
+ WritePS( pFile, "%%BeginProlog\n" );
+
+ // JobPatchFile feature needs to be emitted at begin of prolog
+ writeJobPatch( pFile, rJobData );
+
+ static const char pProlog[] = {
+ "%%BeginResource: procset PSPrint-Prolog 1.0 0\n"
+ "/ISO1252Encoding [\n"
+ "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n"
+ "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n"
+ "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n"
+ "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n"
+ "/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle\n"
+ "/parenleft /parenright /asterisk /plus /comma /hyphen /period /slash\n"
+ "/zero /one /two /three /four /five /six /seven\n"
+ "/eight /nine /colon /semicolon /less /equal /greater /question\n"
+ "/at /A /B /C /D /E /F /G\n"
+ "/H /I /J /K /L /M /N /O\n"
+ "/P /Q /R /S /T /U /V /W\n"
+ "/X /Y /Z /bracketleft /backslash /bracketright /asciicircum /underscore\n"
+ "/grave /a /b /c /d /e /f /g\n"
+ "/h /i /j /k /l /m /n /o\n"
+ "/p /q /r /s /t /u /v /w\n"
+ "/x /y /z /braceleft /bar /braceright /asciitilde /unused\n"
+ "/Euro /unused /quotesinglbase /florin /quotedblbase /ellipsis /dagger /daggerdbl\n"
+ "/circumflex /perthousand /Scaron /guilsinglleft /OE /unused /Zcaron /unused\n"
+ "/unused /quoteleft /quoteright /quotedblleft /quotedblright /bullet /endash /emdash\n"
+ "/tilde /trademark /scaron /guilsinglright /oe /unused /zcaron /Ydieresis\n"
+ "/space /exclamdown /cent /sterling /currency /yen /brokenbar /section\n"
+ "/dieresis /copyright /ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron\n"
+ "/degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /periodcentered\n"
+ "/cedilla /onesuperior /ordmasculine /guillemotright /onequarter /onehalf /threequarters /questiondown\n"
+ "/Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla\n"
+ "/Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis\n"
+ "/Eth /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply\n"
+ "/Oslash /Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /germandbls\n"
+ "/agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla\n"
+ "/egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis\n"
+ "/eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide\n"
+ "/oslash /ugrave /uacute /ucircumflex /udieresis /yacute /thorn /ydieresis] def\n"
+ "\n"
+ "/psp_definefont { exch dup findfont dup length dict begin { 1 index /FID ne\n"
+ "{ def } { pop pop } ifelse } forall /Encoding 3 -1 roll def\n"
+ "currentdict end exch pop definefont pop } def\n"
+ "\n"
+ "/pathdict dup 8 dict def load begin\n"
+ "/rcmd { { currentfile 1 string readstring pop 0 get dup 32 gt { exit }\n"
+ "{ pop } ifelse } loop dup 126 eq { pop exit } if 65 sub dup 16#3 and 1\n"
+ "add exch dup 16#C and -2 bitshift 16#3 and 1 add exch 16#10 and 16#10\n"
+ "eq 3 1 roll exch } def\n"
+ "/rhex { dup 1 sub exch currentfile exch string readhexstring pop dup 0\n"
+ "get dup 16#80 and 16#80 eq dup 3 1 roll { 16#7f and } if 2 index 0 3\n"
+ "-1 roll put 3 1 roll 0 0 1 5 -1 roll { 2 index exch get add 256 mul }\n"
+ "for 256 div exch pop exch { neg } if } def\n"
+ "/xcmd { rcmd exch rhex exch rhex exch 5 -1 roll add exch 4 -1 roll add\n"
+ "1 index 1 index 5 -1 roll { moveto } { lineto } ifelse } def end\n"
+ "/readpath { 0 0 pathdict begin { xcmd } loop end pop pop } def\n"
+ "\n"
+ "systemdict /languagelevel known not {\n"
+ "/xshow { exch dup length 0 1 3 -1 roll 1 sub { dup 3 index exch get\n"
+ "exch 2 index exch get 1 string dup 0 4 -1 roll put currentpoint 3 -1\n"
+ "roll show moveto 0 rmoveto } for pop pop } def\n"
+ "/rectangle { 4 -2 roll moveto 1 index 0 rlineto 0 exch rlineto neg 0\n"
+ "rlineto closepath } def\n"
+ "/rectfill { rectangle fill } def\n"
+ "/rectstroke { rectangle stroke } def } if\n"
+ "/bshow { currentlinewidth 3 1 roll currentpoint 3 index show moveto\n"
+ "setlinewidth false charpath stroke setlinewidth } def\n"
+ "/bxshow { currentlinewidth 4 1 roll setlinewidth exch dup length 1 sub\n"
+ "0 1 3 -1 roll { 1 string 2 index 2 index get 1 index exch 0 exch put dup\n"
+ "currentpoint 3 -1 roll show moveto currentpoint 3 -1 roll false charpath\n"
+ "stroke moveto 2 index exch get 0 rmoveto } for pop pop setlinewidth } def\n"
+ "\n"
+ "/psp_lzwfilter { currentfile /ASCII85Decode filter /LZWDecode filter } def\n"
+ "/psp_ascii85filter { currentfile /ASCII85Decode filter } def\n"
+ "/psp_lzwstring { psp_lzwfilter 1024 string readstring } def\n"
+ "/psp_ascii85string { psp_ascii85filter 1024 string readstring } def\n"
+ "/psp_imagedict {\n"
+ "/psp_bitspercomponent { 3 eq { 1 }{ 8 } ifelse } def\n"
+ "/psp_decodearray { [ [0 1 0 1 0 1] [0 255] [0 1] [0 255] ] exch get }\n"
+ "def 7 dict dup\n"
+ "/ImageType 1 put dup\n"
+ "/Width 7 -1 roll put dup\n"
+ "/Height 5 index put dup\n"
+ "/BitsPerComponent 4 index psp_bitspercomponent put dup\n"
+ "/Decode 5 -1 roll psp_decodearray put dup\n"
+ "/ImageMatrix [1 0 0 1 0 0] dup 5 8 -1 roll put put dup\n"
+ "/DataSource 4 -1 roll 1 eq { psp_lzwfilter } { psp_ascii85filter } ifelse put\n"
+ "} def\n"
+ "%%EndResource\n"
+ "%%EndProlog\n"
+ };
+ WritePS (pFile, pProlog);
+}
+
+bool PrinterJob::writeSetup( osl::File* pFile, const JobData& rJob )
+{
+ WritePS (pFile, "%%BeginSetup\n%\n");
+
+ // download fonts
+ std::vector< OString > aFonts;
+ m_pGraphics->writeResources( pFile, aFonts );
+
+ if( !aFonts.empty() )
+ {
+ std::vector< OString >::const_iterator it = aFonts.begin();
+ OStringBuffer aLine( 256 );
+ aLine.append( "%%DocumentSuppliedResources: font " );
+ aLine.append( *it );
+ aLine.append( "\n" );
+ WritePS ( pFile, aLine.getStr() );
+ while( (++it) != aFonts.end() )
+ {
+ aLine.setLength(0);
+ aLine.append( "%%+ font " );
+ aLine.append( *it );
+ aLine.append( "\n" );
+ WritePS ( pFile, aLine.getStr() );
+ }
+ }
+
+ bool bSuccess = true;
+ // in case of external print dialog the number of copies is prepended
+ // to the job, let us not complicate things by emitting our own copy count
+ bool bExternalDialog = PrinterInfoManager::get().checkFeatureToken( GetPrinterName(), "external_dialog" );
+ if( ! bExternalDialog && rJob.m_nCopies > 1 )
+ {
+ // setup code
+ OString aLine = "/#copies " +
+ OString::number(static_cast<sal_Int32>(rJob.m_nCopies)) +
+ " def\n";
+ sal_uInt64 nWritten = 0;
+ bSuccess = !(pFile->write(aLine.getStr(), aLine.getLength(), nWritten)
+ || nWritten != static_cast<sal_uInt64>(aLine.getLength()));
+
+ if( bSuccess && GetPostscriptLevel( &rJob ) >= 2 )
+ WritePS (pFile, "<< /NumCopies null /Policies << /NumCopies 1 >> >> setpagedevice\n" );
+ }
+
+ bool bFeatureSuccess = writeFeatureList( pFile, rJob, true );
+
+ WritePS (pFile, "%%EndSetup\n");
+
+ return bSuccess && bFeatureSuccess;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/prtsetup.cxx b/vcl/unx/generic/print/prtsetup.cxx
new file mode 100644
index 000000000..56ee475e7
--- /dev/null
+++ b/vcl/unx/generic/print/prtsetup.cxx
@@ -0,0 +1,516 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "prtsetup.hxx"
+#include <svdata.hxx>
+#include <strings.hrc>
+
+#include <officecfg/Office/Common.hxx>
+
+using namespace psp;
+
+void RTSDialog::insertAllPPDValues(weld::ComboBox& rBox, const PPDParser* pParser, const PPDKey* pKey )
+{
+ if( ! pKey || ! pParser )
+ return;
+
+ const PPDValue* pValue = nullptr;
+ OUString aOptionText;
+
+ for (int i = 0; i < pKey->countValues(); ++i)
+ {
+ pValue = pKey->getValue( i );
+ if (pValue->m_bCustomOption)
+ continue;
+ aOptionText = pParser->translateOption( pKey->getKey(), pValue->m_aOption) ;
+
+ OUString sId(weld::toId(pValue));
+ int nCurrentPos = rBox.find_id(sId);
+ if( m_aJobData.m_aContext.checkConstraints( pKey, pValue ) )
+ {
+ if (nCurrentPos == -1)
+ rBox.append(sId, aOptionText);
+ }
+ else
+ {
+ if (nCurrentPos != -1)
+ rBox.remove(nCurrentPos);
+ }
+ }
+ pValue = m_aJobData.m_aContext.getValue( pKey );
+ if (pValue && !pValue->m_bCustomOption)
+ {
+ OUString sId(weld::toId(pValue));
+ int nPos = rBox.find_id(sId);
+ if (nPos != -1)
+ rBox.set_active(nPos);
+ }
+}
+
+/*
+ * RTSDialog
+ */
+
+RTSDialog::RTSDialog(const PrinterInfo& rJobData, weld::Window* pParent)
+ : GenericDialogController(pParent, "vcl/ui/printerpropertiesdialog.ui", "PrinterPropertiesDialog")
+ , m_aJobData(rJobData)
+ , m_bDataModified(false)
+ , m_xTabControl(m_xBuilder->weld_notebook("tabcontrol"))
+ , m_xOKButton(m_xBuilder->weld_button("ok"))
+ , m_xCancelButton(m_xBuilder->weld_button("cancel"))
+ , m_xPaperPage(new RTSPaperPage(m_xTabControl->get_page("paper"), this))
+ , m_xDevicePage(new RTSDevicePage(m_xTabControl->get_page("device"), this))
+{
+ OUString aTitle(m_xDialog->get_title());
+ m_xDialog->set_title(aTitle.replaceAll("%s", m_aJobData.m_aPrinterName));
+
+ m_xTabControl->connect_enter_page( LINK( this, RTSDialog, ActivatePage ) );
+ m_xOKButton->connect_clicked( LINK( this, RTSDialog, ClickButton ) );
+ m_xCancelButton->connect_clicked( LINK( this, RTSDialog, ClickButton ) );
+ ActivatePage(m_xTabControl->get_current_page_ident());
+}
+
+RTSDialog::~RTSDialog()
+{
+}
+
+IMPL_LINK(RTSDialog, ActivatePage, const OString&, rPage, void)
+{
+ if (rPage == "paper")
+ m_xPaperPage->update();
+}
+
+IMPL_LINK( RTSDialog, ClickButton, weld::Button&, rButton, void )
+{
+ if (&rButton == m_xOKButton.get())
+ {
+ // refresh the changed values
+ if (m_xPaperPage)
+ {
+ // orientation
+ m_aJobData.m_eOrientation = m_xPaperPage->getOrientation() == 0 ?
+ orientation::Portrait : orientation::Landscape;
+ // assume use of paper size from printer setup if the user
+ // got here via File > Printer Settings ...
+ if ( m_aJobData.meSetupMode == PrinterSetupMode::DocumentGlobal )
+ m_aJobData.m_bPapersizeFromSetup = true;
+ }
+ if( m_xDevicePage )
+ {
+ m_aJobData.m_nColorDepth = m_xDevicePage->getDepth();
+ m_aJobData.m_nColorDevice = m_xDevicePage->getColorDevice();
+ m_aJobData.m_nPSLevel = m_xDevicePage->getLevel();
+ m_aJobData.m_nPDFDevice = m_xDevicePage->getPDFDevice();
+ }
+ m_xDialog->response(RET_OK);
+ }
+ else if (&rButton == m_xCancelButton.get())
+ m_xDialog->response(RET_CANCEL);
+}
+
+/*
+ * RTSPaperPage
+ */
+
+RTSPaperPage::RTSPaperPage(weld::Widget* pPage, RTSDialog* pDialog)
+ : m_xBuilder(Application::CreateBuilder(pPage, "vcl/ui/printerpaperpage.ui"))
+ , m_pParent(pDialog)
+ , m_xContainer(m_xBuilder->weld_widget("PrinterPaperPage"))
+ , m_xCbFromSetup(m_xBuilder->weld_check_button("papersizefromsetup"))
+ , m_xPaperText(m_xBuilder->weld_label("paperft"))
+ , m_xPaperBox(m_xBuilder->weld_combo_box("paperlb"))
+ , m_xOrientText(m_xBuilder->weld_label("orientft"))
+ , m_xOrientBox(m_xBuilder->weld_combo_box("orientlb"))
+ , m_xDuplexText(m_xBuilder->weld_label("duplexft"))
+ , m_xDuplexBox(m_xBuilder->weld_combo_box("duplexlb"))
+ , m_xSlotText(m_xBuilder->weld_label("slotft"))
+ , m_xSlotBox(m_xBuilder->weld_combo_box("slotlb"))
+{
+ //PrinterPaperPage
+ m_xPaperBox->connect_changed( LINK( this, RTSPaperPage, SelectHdl ) );
+ m_xOrientBox->connect_changed( LINK( this, RTSPaperPage, SelectHdl ) );
+ m_xDuplexBox->connect_changed( LINK( this, RTSPaperPage, SelectHdl ) );
+ m_xSlotBox->connect_changed( LINK( this, RTSPaperPage, SelectHdl ) );
+ m_xCbFromSetup->connect_toggled( LINK( this, RTSPaperPage, CheckBoxHdl ) );
+
+ update();
+}
+
+RTSPaperPage::~RTSPaperPage()
+{
+}
+
+void RTSPaperPage::update()
+{
+ const PPDKey* pKey = nullptr;
+
+ // orientation
+ m_xOrientBox->set_active(m_pParent->m_aJobData.m_eOrientation == orientation::Portrait ? 0 : 1);
+
+ // duplex
+ if( m_pParent->m_aJobData.m_pParser &&
+ (pKey = m_pParent->m_aJobData.m_pParser->getKey( "Duplex" )) )
+ {
+ m_pParent->insertAllPPDValues( *m_xDuplexBox, m_pParent->m_aJobData.m_pParser, pKey );
+ }
+ else
+ {
+ m_xDuplexText->set_sensitive( false );
+ m_xDuplexBox->set_sensitive( false );
+ }
+
+ // paper
+ if( m_pParent->m_aJobData.m_pParser &&
+ (pKey = m_pParent->m_aJobData.m_pParser->getKey( "PageSize" )) )
+ {
+ m_pParent->insertAllPPDValues( *m_xPaperBox, m_pParent->m_aJobData.m_pParser, pKey );
+ }
+ else
+ {
+ m_xPaperText->set_sensitive( false );
+ m_xPaperBox->set_sensitive( false );
+ }
+
+ // input slots
+ if( m_pParent->m_aJobData.m_pParser &&
+ (pKey = m_pParent->m_aJobData.m_pParser->getKey( "InputSlot" )) )
+ {
+ m_pParent->insertAllPPDValues( *m_xSlotBox, m_pParent->m_aJobData.m_pParser, pKey );
+ }
+ else
+ {
+ m_xSlotText->set_sensitive( false );
+ m_xSlotBox->set_sensitive( false );
+ }
+
+ if ( m_pParent->m_aJobData.meSetupMode != PrinterSetupMode::SingleJob )
+ return;
+
+ m_xCbFromSetup->show();
+
+ if ( m_pParent->m_aJobData.m_bPapersizeFromSetup )
+ m_xCbFromSetup->set_active(m_pParent->m_aJobData.m_bPapersizeFromSetup);
+ // disable those, unless user wants to use papersize from printer prefs
+ // as they have no influence on what's going to be printed anyway
+ else
+ {
+ m_xPaperText->set_sensitive( false );
+ m_xPaperBox->set_sensitive( false );
+ m_xOrientText->set_sensitive( false );
+ m_xOrientBox->set_sensitive( false );
+ }
+}
+
+IMPL_LINK( RTSPaperPage, SelectHdl, weld::ComboBox&, rBox, void )
+{
+ const PPDKey* pKey = nullptr;
+ if( &rBox == m_xPaperBox.get() )
+ {
+ if( m_pParent->m_aJobData.m_pParser )
+ pKey = m_pParent->m_aJobData.m_pParser->getKey( "PageSize" );
+ }
+ else if( &rBox == m_xDuplexBox.get() )
+ {
+ if( m_pParent->m_aJobData.m_pParser )
+ pKey = m_pParent->m_aJobData.m_pParser->getKey( "Duplex" );
+ }
+ else if( &rBox == m_xSlotBox.get() )
+ {
+ if( m_pParent->m_aJobData.m_pParser )
+ pKey = m_pParent->m_aJobData.m_pParser->getKey( "InputSlot" );
+ }
+ else if( &rBox == m_xOrientBox.get() )
+ {
+ m_pParent->m_aJobData.m_eOrientation = m_xOrientBox->get_active() == 0 ? orientation::Portrait : orientation::Landscape;
+ }
+ if( pKey )
+ {
+ PPDValue* pValue = weld::fromId<PPDValue*>(rBox.get_active_id());
+ m_pParent->m_aJobData.m_aContext.setValue( pKey, pValue );
+ update();
+ }
+
+ m_pParent->SetDataModified( true );
+}
+
+IMPL_LINK_NOARG(RTSPaperPage, CheckBoxHdl, weld::Toggleable&, void)
+{
+ bool bFromSetup = m_xCbFromSetup->get_active();
+ m_pParent->m_aJobData.m_bPapersizeFromSetup = bFromSetup;
+ m_xPaperText->set_sensitive(bFromSetup);
+ m_xPaperBox->set_sensitive(bFromSetup);
+ m_xOrientText->set_sensitive(bFromSetup);
+ m_xOrientBox->set_sensitive(bFromSetup);
+ m_pParent->SetDataModified(true);
+}
+
+/*
+ * RTSDevicePage
+ */
+RTSDevicePage::RTSDevicePage(weld::Widget* pPage, RTSDialog* pParent)
+ : m_xBuilder(Application::CreateBuilder(pPage, "vcl/ui/printerdevicepage.ui"))
+ , m_pCustomValue(nullptr)
+ , m_pParent(pParent)
+ , m_xContainer(m_xBuilder->weld_widget("PrinterDevicePage"))
+ , m_xPPDKeyBox(m_xBuilder->weld_tree_view("options"))
+ , m_xPPDValueBox(m_xBuilder->weld_tree_view("values"))
+ , m_xCustomEdit(m_xBuilder->weld_entry("custom"))
+ , m_xLevelBox(m_xBuilder->weld_combo_box("level"))
+ , m_xSpaceBox(m_xBuilder->weld_combo_box("colorspace"))
+ , m_xDepthBox(m_xBuilder->weld_combo_box("colordepth"))
+ , m_aReselectCustomIdle("RTSDevicePage m_aReselectCustomIdle")
+{
+ m_aReselectCustomIdle.SetInvokeHandler(LINK(this, RTSDevicePage, ImplHandleReselectHdl));
+
+ m_xPPDKeyBox->set_size_request(m_xPPDKeyBox->get_approximate_digit_width() * 32,
+ m_xPPDKeyBox->get_height_rows(12));
+
+ m_xCustomEdit->connect_changed(LINK(this, RTSDevicePage, ModifyHdl));
+
+ m_xPPDKeyBox->connect_changed( LINK( this, RTSDevicePage, SelectHdl ) );
+ m_xPPDValueBox->connect_changed( LINK( this, RTSDevicePage, SelectHdl ) );
+
+ m_xLevelBox->connect_changed(LINK(this, RTSDevicePage, ComboChangedHdl));
+ m_xSpaceBox->connect_changed(LINK(this, RTSDevicePage, ComboChangedHdl));
+ m_xDepthBox->connect_changed(LINK(this, RTSDevicePage, ComboChangedHdl));
+
+ switch( m_pParent->m_aJobData.m_nColorDevice )
+ {
+ case 0:
+ m_xSpaceBox->set_active(0);
+ break;
+ case 1:
+ m_xSpaceBox->set_active(1);
+ break;
+ case -1:
+ m_xSpaceBox->set_active(2);
+ break;
+ }
+
+ sal_Int32 nLevelEntryData = 0; //automatic
+ if( m_pParent->m_aJobData.m_nPDFDevice == 2 ) //explicit PDF
+ nLevelEntryData = 10;
+ else if (m_pParent->m_aJobData.m_nPSLevel > 0) //explicit PS Level
+ nLevelEntryData = m_pParent->m_aJobData.m_nPSLevel+1;
+ else if (m_pParent->m_aJobData.m_nPDFDevice == 1) //automatically PDF
+ nLevelEntryData = 0;
+ else if (m_pParent->m_aJobData.m_nPDFDevice == -1) //explicitly PS from driver
+ nLevelEntryData = 1;
+
+ bool bAutoIsPDF = officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::get();
+
+ assert(nLevelEntryData != 0
+ || "Generic Printer" == m_pParent->m_aJobData.m_aPrinterName
+ || int(bAutoIsPDF) == m_pParent->m_aJobData.m_nPDFDevice);
+
+ OUString sStr = m_xLevelBox->get_text(0);
+ OUString sId = m_xLevelBox->get_id(0);
+ m_xLevelBox->insert(0, sStr.replaceAll("%s", bAutoIsPDF ? m_xLevelBox->get_text(5) : m_xLevelBox->get_text(1)), &sId, nullptr, nullptr);
+ m_xLevelBox->remove(1);
+
+ for (int i = 0; i < m_xLevelBox->get_count(); ++i)
+ {
+ if (m_xLevelBox->get_id(i).toInt32() == nLevelEntryData)
+ {
+ m_xLevelBox->set_active(i);
+ break;
+ }
+ }
+
+ if (m_pParent->m_aJobData.m_nColorDepth == 8)
+ m_xDepthBox->set_active(0);
+ else if (m_pParent->m_aJobData.m_nColorDepth == 24)
+ m_xDepthBox->set_active(1);
+
+ // fill ppd boxes
+ if( !m_pParent->m_aJobData.m_pParser )
+ return;
+
+ for( int i = 0; i < m_pParent->m_aJobData.m_pParser->getKeys(); i++ )
+ {
+ const PPDKey* pKey = m_pParent->m_aJobData.m_pParser->getKey( i );
+
+ // skip options already shown somewhere else
+ // also skip options from the "InstallableOptions" PPD group
+ // Options in that group define hardware features that are not
+ // job-specific and should better be handled in the system-wide
+ // printer configuration. Keyword is defined in PPD specification
+ // (version 4.3), section 5.4.
+ if( pKey->isUIKey() &&
+ pKey->getKey() != "PageSize" &&
+ pKey->getKey() != "InputSlot" &&
+ pKey->getKey() != "PageRegion" &&
+ pKey->getKey() != "Duplex" &&
+ pKey->getGroup() != "InstallableOptions")
+ {
+ OUString aEntry( m_pParent->m_aJobData.m_pParser->translateKey( pKey->getKey() ) );
+ m_xPPDKeyBox->append(weld::toId(pKey), aEntry);
+ }
+ }
+}
+
+RTSDevicePage::~RTSDevicePage()
+{
+}
+
+sal_uLong RTSDevicePage::getDepth() const
+{
+ sal_uInt16 nSelectPos = m_xDepthBox->get_active();
+ if (nSelectPos == 0)
+ return 8;
+ else
+ return 24;
+}
+
+sal_uLong RTSDevicePage::getColorDevice() const
+{
+ sal_uInt16 nSelectPos = m_xSpaceBox->get_active();
+ switch (nSelectPos)
+ {
+ case 0:
+ return 0;
+ case 1:
+ return 1;
+ case 2:
+ return -1;
+ }
+ return 0;
+}
+
+sal_uLong RTSDevicePage::getLevel() const
+{
+ auto nLevel = m_xLevelBox->get_active_id().toInt32();
+ if (nLevel == 0)
+ return 0; //automatic
+ return nLevel < 10 ? nLevel-1 : 0;
+}
+
+sal_uLong RTSDevicePage::getPDFDevice() const
+{
+ auto nLevel = m_xLevelBox->get_active_id().toInt32();
+ if (nLevel > 9)
+ return 2; //explicitly PDF
+ else if (nLevel == 0)
+ return 0; //automatic
+ return -1; //explicitly PS
+}
+
+IMPL_LINK(RTSDevicePage, ModifyHdl, weld::Entry&, rEdit, void)
+{
+ if (m_pCustomValue)
+ {
+ // tdf#123734 Custom PPD option values are a CUPS extension to PPDs and the user-set value
+ // needs to be prefixed with "Custom." in order to be processed properly
+ m_pCustomValue->m_aCustomOption = "Custom." + rEdit.get_text();
+ }
+}
+
+IMPL_LINK( RTSDevicePage, SelectHdl, weld::TreeView&, rBox, void )
+{
+ if (&rBox == m_xPPDKeyBox.get())
+ {
+ const PPDKey* pKey = weld::fromId<PPDKey*>(m_xPPDKeyBox->get_selected_id());
+ FillValueBox( pKey );
+ }
+ else if (&rBox == m_xPPDValueBox.get())
+ {
+ const PPDKey* pKey = weld::fromId<PPDKey*>(m_xPPDKeyBox->get_selected_id());
+ const PPDValue* pValue = weld::fromId<PPDValue*>(m_xPPDValueBox->get_selected_id());
+ if (pKey && pValue)
+ {
+ m_pParent->m_aJobData.m_aContext.setValue( pKey, pValue );
+ ValueBoxChanged(pKey);
+ }
+ }
+ m_pParent->SetDataModified( true );
+}
+
+IMPL_LINK_NOARG( RTSDevicePage, ComboChangedHdl, weld::ComboBox&, void )
+{
+ m_pParent->SetDataModified( true );
+}
+
+void RTSDevicePage::FillValueBox( const PPDKey* pKey )
+{
+ m_xPPDValueBox->clear();
+ m_xCustomEdit->hide();
+
+ if( ! pKey )
+ return;
+
+ const PPDValue* pValue = nullptr;
+ for( int i = 0; i < pKey->countValues(); i++ )
+ {
+ pValue = pKey->getValue( i );
+ if( m_pParent->m_aJobData.m_aContext.checkConstraints( pKey, pValue ) &&
+ m_pParent->m_aJobData.m_pParser )
+ {
+ OUString aEntry;
+ if (pValue->m_bCustomOption)
+ aEntry = VclResId(SV_PRINT_CUSTOM_TXT);
+ else
+ aEntry = m_pParent->m_aJobData.m_pParser->translateOption( pKey->getKey(), pValue->m_aOption);
+ m_xPPDValueBox->append(weld::toId(pValue), aEntry);
+ }
+ }
+ pValue = m_pParent->m_aJobData.m_aContext.getValue( pKey );
+ m_xPPDValueBox->select_id(weld::toId(pValue));
+
+ ValueBoxChanged(pKey);
+}
+
+IMPL_LINK_NOARG(RTSDevicePage, ImplHandleReselectHdl, Timer*, void)
+{
+ //in case selected entry is now not visible select it again to scroll it into view
+ m_xPPDValueBox->select(m_xPPDValueBox->get_selected_index());
+}
+
+void RTSDevicePage::ValueBoxChanged( const PPDKey* pKey )
+{
+ const PPDValue* pValue = m_pParent->m_aJobData.m_aContext.getValue(pKey);
+ if (pValue->m_bCustomOption)
+ {
+ m_pCustomValue = pValue;
+ m_pParent->m_aJobData.m_aContext.setValue(pKey, pValue);
+ // don't show the "Custom." prefix in the UI, s.a. comment in ModifyHdl
+ m_xCustomEdit->set_text(m_pCustomValue->m_aCustomOption.replaceFirst("Custom.", ""));
+ m_xCustomEdit->show();
+ m_aReselectCustomIdle.Start();
+ }
+ else
+ m_xCustomEdit->hide();
+}
+
+int SetupPrinterDriver(weld::Window* pParent, ::psp::PrinterInfo& rJobData)
+{
+ int nRet = 0;
+ RTSDialog aDialog(rJobData, pParent);
+
+ // return 0 if cancel was pressed or if the data
+ // weren't modified, 1 otherwise
+ if (aDialog.run() != RET_CANCEL)
+ {
+ rJobData = aDialog.getSetup();
+ nRet = aDialog.GetDataModified() ? 1 : 0;
+ }
+
+ return nRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/prtsetup.hxx b/vcl/unx/generic/print/prtsetup.hxx
new file mode 100644
index 000000000..bcf86670d
--- /dev/null
+++ b/vcl/unx/generic/print/prtsetup.hxx
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <vcl/idle.hxx>
+#include <vcl/weld.hxx>
+#include <ppdparser.hxx>
+#include <printerinfomanager.hxx>
+
+class RTSPaperPage;
+class RTSDevicePage;
+
+class RTSDialog : public weld::GenericDialogController
+{
+ friend class RTSPaperPage;
+ friend class RTSDevicePage;
+
+ ::psp::PrinterInfo m_aJobData;
+
+ bool m_bDataModified;
+
+ // controls
+ std::unique_ptr<weld::Notebook> m_xTabControl;
+ std::unique_ptr<weld::Button> m_xOKButton;
+ std::unique_ptr<weld::Button> m_xCancelButton;
+
+ // pages
+ std::unique_ptr<RTSPaperPage> m_xPaperPage;
+ std::unique_ptr<RTSDevicePage> m_xDevicePage;
+
+ DECL_LINK(ActivatePage, const OString&, void);
+ DECL_LINK(ClickButton, weld::Button&, void);
+
+ // helper functions
+ void insertAllPPDValues(weld::ComboBox&, const psp::PPDParser*, const psp::PPDKey*);
+
+public:
+ RTSDialog(const ::psp::PrinterInfo& rJobData, weld::Window* pParent);
+ virtual ~RTSDialog() override;
+
+ const ::psp::PrinterInfo& getSetup() const { return m_aJobData; }
+
+ void SetDataModified(bool bModified) { m_bDataModified = bModified; }
+ bool GetDataModified() const { return m_bDataModified; }
+};
+
+class RTSPaperPage
+{
+private:
+ std::unique_ptr<weld::Builder> m_xBuilder;
+
+ RTSDialog* m_pParent;
+
+ std::unique_ptr<weld::Widget> m_xContainer;
+
+ std::unique_ptr<weld::CheckButton> m_xCbFromSetup;
+
+ std::unique_ptr<weld::Label> m_xPaperText;
+ std::unique_ptr<weld::ComboBox> m_xPaperBox;
+
+ std::unique_ptr<weld::Label> m_xOrientText;
+ std::unique_ptr<weld::ComboBox> m_xOrientBox;
+
+ std::unique_ptr<weld::Label> m_xDuplexText;
+ std::unique_ptr<weld::ComboBox> m_xDuplexBox;
+
+ std::unique_ptr<weld::Label> m_xSlotText;
+ std::unique_ptr<weld::ComboBox> m_xSlotBox;
+
+ DECL_LINK(SelectHdl, weld::ComboBox&, void);
+ DECL_LINK(CheckBoxHdl, weld::Toggleable&, void);
+
+public:
+ RTSPaperPage(weld::Widget* pPage, RTSDialog* pDialog);
+ ~RTSPaperPage();
+
+ void update();
+
+ sal_Int32 getOrientation() const { return m_xOrientBox->get_active(); }
+};
+
+class RTSDevicePage
+{
+private:
+ std::unique_ptr<weld::Builder> m_xBuilder;
+
+ const psp::PPDValue* m_pCustomValue;
+ RTSDialog* m_pParent;
+
+ std::unique_ptr<weld::Widget> m_xContainer;
+ std::unique_ptr<weld::TreeView> m_xPPDKeyBox;
+ std::unique_ptr<weld::TreeView> m_xPPDValueBox;
+ std::unique_ptr<weld::Entry> m_xCustomEdit;
+
+ std::unique_ptr<weld::ComboBox> m_xLevelBox;
+ std::unique_ptr<weld::ComboBox> m_xSpaceBox;
+ std::unique_ptr<weld::ComboBox> m_xDepthBox;
+
+ void FillValueBox(const ::psp::PPDKey*);
+ void ValueBoxChanged(const ::psp::PPDKey*);
+
+ Idle m_aReselectCustomIdle;
+
+ DECL_LINK(SelectHdl, weld::TreeView&, void);
+ DECL_LINK(ModifyHdl, weld::Entry&, void);
+ DECL_LINK(ComboChangedHdl, weld::ComboBox&, void);
+ DECL_LINK(ImplHandleReselectHdl, Timer*, void);
+
+public:
+ RTSDevicePage(weld::Widget* pPage, RTSDialog* pDialog);
+ ~RTSDevicePage();
+
+ sal_uLong getLevel() const;
+ sal_uLong getPDFDevice() const;
+ sal_uLong getDepth() const;
+ sal_uLong getColorDevice() const;
+};
+
+int SetupPrinterDriver(weld::Window* pParent, ::psp::PrinterInfo& rJobData);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/psheader.ps b/vcl/unx/generic/print/psheader.ps
new file mode 100644
index 000000000..49f0f5101
--- /dev/null
+++ b/vcl/unx/generic/print/psheader.ps
@@ -0,0 +1,363 @@
+%
+% This file is part of the LibreOffice project.
+%
+% This Source Code Form is subject to the terms of the Mozilla Public
+% License, v. 2.0. If a copy of the MPL was not distributed with this
+% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%
+% This file incorporates work covered by the following license notice:
+%
+% Licensed to the Apache Software Foundation (ASF) under one or more
+% contributor license agreements. See the NOTICE file distributed
+% with this work for additional information regarding copyright
+% ownership. The ASF licenses this file to you under the Apache
+% License, Version 2.0 (the "License"); you may not use this file
+% except in compliance with the License. You may obtain a copy of
+% the License at http://www.apache.org/licenses/LICENSE-2.0 .
+%
+
+% This is an "unobsfucated version of postscript header" in printerjob.cxx. It
+% was probably kept separate for the comments, but it is not used in itself
+% and probably was not kept in sync with the actual header.
+
+%
+%
+% readpath
+%
+% The intention of readpath is to save disk space since the vcl clip region routines
+% produce a huge amount of lineto/moveto commands
+%
+% The principal idea is to maintain the current point on stack and to provide only deltas
+% in the command. These deltas are added to the current point. The new point is used for
+% the lineto and moveto command and saved on stack for the next command.
+%
+% pathdict implements binary/hex representation of lineto and moveto commands.
+% The command consists of a 1byte opcode to switch between lineto and moveto and the size
+% of the following delta-x and delta-y values. The opcode is read with /rcmd, the two
+% coordinates are read with /rhex. The whole command is executed with /xcmd
+%
+%
+
+/pathdict dup 8 dict def load
+begin
+
+ % the command is of the bit format cxxyy
+ % with c=0 meaning lineto
+ % c=1 meaning moveto
+ % xx is a 2bit value for the number of bytes for x position
+ % yy is the same for y, values are off by one: 00 means 1; 11 means 4 !
+ % the command has been added to 'A' to be always in the ascii character
+ % range. the command is followed by 2*xx + 2*yy hexchars.
+ % '~' denotes the special case of EOD
+ /rcmd {
+ {
+ currentfile 1 string readstring % s bool
+ pop % s
+ 0 get % s[0]
+ % --- check whether s[0] is CR, LF ...
+ dup 32 gt % s > ' ' ? then read on
+ { exit }
+ { pop }
+ ifelse
+ }
+ loop
+
+ dup 126 eq { pop exit } if % -- Exit loop if cmd is '~'
+ 65 sub % cmd=s[0]-'A'
+ % -- Separate yy bits
+ dup 16#3 and 1 add % cmd yy
+ % -- Separate xx bits
+ exch % yy cmd
+ dup 16#C and -2 bitshift
+ 16#3 and 1 add exch % yy xx cmd
+ % -- Separate command bit
+ 16#10 and 16#10 eq % yy xx bool
+ 3 1 roll exch % bool xx yy
+ } def
+
+ % length rhex -- reads a signed hex value of given length
+ % the left most bit of char 0 is considered as the sign (0 means '+', 1 means '-')
+ % the rest of the bits is considered to be the abs value. Please note that this
+ % does not match the C binary representation of integers
+ /rhex {
+ dup 1 sub exch % l-1 l
+ currentfile exch string readhexstring % l-1 substring[l] bool
+ pop
+ dup 0 get dup % l-1 s s[0] s[0]
+ % -- Extract the sign
+ 16#80 and 16#80 eq dup % l-1 s s[0] sign=- sign=-
+ % -- Mask out the sign bit and put value back
+ 3 1 roll % l-1 s sign=- s[0] sign=-
+ { 16#7f and } if % l-1 s sign=- +s[0]
+ 2 index 0 % l-1 s sign=- +s[0] s 0
+ 3 -1 roll put % l-1 s sign=- s 0 +s[0]
+ % -- Read loop: add to prev sum, mul with 256
+ 3 1 roll 0 % sign=- l-1 s Sum=0
+ 0 1 5 -1 roll % sign=- s Sum=0 0 1 l-1
+ { % sign=- s Sum idx
+ 2 index exch % sign=- s Sum s idx
+ get % sign=- s Sum s[idx]
+ add 256 mul % sign=- s Sum=(s[idx]+Sum)*256
+ }
+ for
+ % -- mul was once too often, weave in the sign
+ 256 div % sign=- s Sum/256
+ exch pop % sign=- Sum/256
+ exch { neg } if % (sign=- ? -Sum : Sum)
+ } def
+
+ % execute a single command, the former x and y position is already on stack
+ % only offsets are read from cmdstring
+ /xcmd { % x y
+ rcmd % x y bool wx wy
+ exch rhex % x y bool wy Dx
+ exch rhex % x y bool Dx Dy
+ exch 5 -1 roll % y bool Dy Dx x
+ add exch % y bool X Dy
+ 4 -1 roll add % bool X Y
+ 1 index 1 index % bool X Y X Y
+ 5 -1 roll % X Y X Y bool
+ { moveto }
+ { lineto }
+ ifelse % X Y
+ } def
+end
+
+/readpath
+{
+ 0 0 % push initial-x initial-y
+ pathdict begin
+ { xcmd } loop
+ end
+ pop pop % pop final-x final-y
+} def
+
+%
+%
+% if languagelevel is not in the systemdict then its level 1 interpreter:
+% provide compatibility routines
+%
+%
+
+systemdict /languagelevel known not
+{
+ % string numarray xxshow -
+ % does only work for single byte fonts
+ /xshow {
+ exch dup % a s s
+ length 0 1 % a s l(s) 1 1
+ 3 -1 roll 1 sub % a s 0 1 l(s)-1
+ { % a s idx
+ dup % a s idx idx
+ % -- extract the delta offset
+ 3 index exch get % a s idx a[idx]
+ % -- extract the character
+ exch % a s a[idx] idx
+ 2 index exch get % a s a[idx] s[idx]
+ % -- create a tmp string for show
+ 1 string dup 0 % a s a[idx] s[idx] s1 s1 0
+ 4 -1 roll % a s a[idx] s1 s1 0 s[idx]
+ put % a s a[idx] s1
+ % -- store the current point
+ currentpoint 3 -1 roll % a s a[idx] x y s1
+ % -- draw the character
+ show % a s a[idx] x y
+ % -- move to the offset
+ moveto 0 rmoveto % a s
+ }
+ for
+ pop pop % -
+ } def
+
+ % x y width height rectfill
+ % x y width height rectshow
+ % in contrast to the languagelevel 2 operator
+ % they use and change the currentpath
+ /rectangle {
+ 4 -2 roll % width height x y
+ moveto % width height
+ 1 index 0 rlineto % width height % rmoveto(width, 0)
+ 0 exch rlineto % width % rmoveto(0, height)
+ neg 0 rlineto % - % rmoveto(-width, 0)
+ closepath
+ } def
+
+ /rectfill { rectangle fill } def
+ /rectstroke { rectangle stroke } def
+}
+if
+
+% -- small test program
+% 75 75 moveto /Times-Roman findfont 12 scalefont setfont
+% <292a2b2c2d2e2f30313233343536373839>
+% [5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 5] xshow <21>[0] xshow
+% showpage
+
+%
+%
+% shortcuts for image header with compression
+%
+%
+
+/psp_lzwfilter {
+ currentfile /ASCII85Decode filter /LZWDecode filter
+} def
+/psp_ascii85filter {
+ currentfile /ASCII85Decode filter
+} def
+/psp_lzwstring {
+ psp_lzwfilter 1024 string readstring
+} def
+/psp_ascii85string {
+ psp_ascii85filter 1024 string readstring
+} def
+/psp_imagedict {
+ /psp_bitspercomponent {
+ 3 eq
+ { 1 }
+ { 8 }
+ ifelse
+ } def
+ /psp_decodearray {
+ [ [0 1 0 1 0 1] [0 255] [0 1] [0 255] ] exch get
+ } def
+
+ 7 dict dup
+ /ImageType 1 put dup
+ /Width 7 -1 roll put dup
+ /Height 5 index put dup
+ /BitsPerComponent 4 index
+ psp_bitspercomponent put dup
+ /Decode 5 -1 roll
+ psp_decodearray put dup
+ /ImageMatrix [1 0 0 1 0 0] dup
+ 5 8 -1 roll put put dup
+ /DataSource 4 -1 roll
+ 1 eq
+ { psp_lzwfilter }
+ { psp_ascii85filter }
+ ifelse put
+} def
+
+
+%
+%
+% font encoding and reencoding
+%
+%
+
+/ISO1252Encoding [
+ /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef
+ /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef
+ /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef
+ /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef
+ /space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle
+ /parenleft /parenright /asterisk /plus /comma /hyphen /period /slash
+ /zero /one /two /three /four /five /six /seven
+ /eight /nine /colon /semicolon /less /equal /greater /question
+ /at /A /B /C /D /E /F /G
+ /H /I /J /K /L /M /N /O
+ /P /Q /R /S /T /U /V /W
+ /X /Y /Z /bracketleft /backslash /bracketright /asciicircum /underscore
+ /grave /a /b /c /d /e /f /g
+ /h /i /j /k /l /m /n /o
+ /p /q /r /s /t /u /v /w
+ /x /y /z /braceleft /bar /braceright /asciitilde /unused
+ /Euro /unused /quotesinglbase /florin /quotedblbase /ellipsis /dagger /daggerdbl
+ /circumflex /perthousand /Scaron /guilsinglleft /OE /unused /Zcaron /unused
+ /unused /quoteleft /quoteright /quotedblleft /quotedblright /bullet /endash /emdash
+ /tilde /trademark /scaron /guilsinglright /oe /unused /zcaron /Ydieresis
+ /space /exclamdown /cent /sterling /currency /yen /brokenbar /section
+ /dieresis /copyright /ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron
+ /degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /periodcentered
+ /cedilla /onesuperior /ordmasculine /guillemotright /onequarter /onehalf /threequarters /questiondown
+ /Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla
+ /Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis
+ /Eth /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply
+ /Oslash /Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /germandbls
+ /agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla
+ /egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis
+ /eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide
+ /oslash /ugrave /uacute /ucircumflex /udieresis /yacute /thorn /ydieresis
+] def
+
+% /fontname /encoding psp_findfont
+/psp_findfont {
+ exch dup % encoding fontname fontname
+ findfont % encoding fontname
+ dup length dict
+ begin
+ {
+ 1 index /FID ne
+ { def }
+ { pop pop }
+ ifelse
+ } forall
+ /Encoding 3 -1 roll def
+ currentdict
+ end
+ /psp_reencodedfont exch definefont
+} def
+
+% bshow shows a text in artificial bold
+% this is achieved by first showing the text
+% then stroking its outline over it with
+% the linewidth set to the second parameter
+% usage: (string) num bshow
+
+/bshow {
+ currentlinewidth % save current linewidth
+ 3 1 roll % move it to the last stack position
+ currentpoint % save the current point
+ 3 index % copy the string to show
+ show % show it
+ moveto % move to the original coordinates again
+ setlinewidth % set the linewidth
+ false charpath % create the outline path of the shown string
+ stroke % and stroke it
+ setlinewidth % reset the stored linewidth
+} def
+
+% bxshow shows a text with a delta array in artificial bold
+% that is it does what bshow does for show
+% usage: (string) [deltaarray] num bxshow
+
+/bxshow {
+ currentlinewidth % save linewidth
+ 4 1 roll % move it to the last stack position
+ setlinewidth % set the new linewidth
+ exch % exchange string and delta array
+ dup
+ length % get length of string
+ 1 sub % prepare parameters for {} for
+ 0 1
+ 3 -1 roll
+ {
+ 1 string % create a string object length 1
+ 2 index % get the text
+ 2 index % get charpos (for index variable)
+ get % have char value at charpos
+ 1 index % prepare string for put
+ exch
+ 0
+ exch
+ put % put into string of length 1
+ dup % duplicate the it
+ currentpoint % save current position
+ 3 -1 roll % prepare show
+ show % show the character
+ moveto % move back to beginning
+ currentpoint % save current position
+ 3 -1 roll % prepare outline path of character
+ false charpath
+ stroke % stroke it
+ moveto % move back
+ % now move to next point
+ 2 index % get advance array
+ exch % get charpos
+ get % get advance element
+ 0 rmoveto % advance current position
+ } for
+ pop pop % remove string and delta array
+ setlinewidth % restore linewidth
+} def
diff --git a/vcl/unx/generic/print/psputil.cxx b/vcl/unx/generic/print/psputil.cxx
new file mode 100644
index 000000000..b4837138a
--- /dev/null
+++ b/vcl/unx/generic/print/psputil.cxx
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <string.h>
+#include "psputil.hxx"
+
+namespace psp {
+
+/*
+ * string convenience routines
+ */
+
+sal_Int32
+getHexValueOf (sal_Int32 nValue, OStringBuffer& pBuffer)
+{
+ const static char pHex [0x10] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+ pBuffer.append(pHex [(nValue & 0xF0) >> 4]);
+ pBuffer.append(pHex [(nValue & 0x0F) ]);
+
+ return 2;
+}
+
+sal_Int32
+getAlignedHexValueOf (sal_Int32 nValue, OStringBuffer& pBuffer)
+{
+ // get sign
+ bool bNegative = nValue < 0;
+ nValue = bNegative ? -nValue : nValue;
+
+ // get required buffer size, must be a multiple of two
+ sal_Int32 nPrecision;
+ if (nValue < 0x80)
+ nPrecision = 2;
+ else
+ if (nValue < 0x8000)
+ nPrecision = 4;
+ else
+ if (nValue < 0x800000)
+ nPrecision = 6;
+ else
+ nPrecision = 8;
+
+ // convert the int into its hex representation, write it into the buffer
+ sal_Int32 nRet = nPrecision;
+ auto const start = pBuffer.getLength();
+ while (nPrecision)
+ {
+ OStringBuffer scratch;
+ nPrecision -= getHexValueOf (nValue % 256, scratch );
+ pBuffer.insert(start, scratch);
+ nValue /= 256;
+ }
+
+ // set sign bit
+ if (bNegative)
+ {
+ switch (pBuffer[start])
+ {
+ case '0' : pBuffer[start] = '8'; break;
+ case '1' : pBuffer[start] = '9'; break;
+ case '2' : pBuffer[start] = 'A'; break;
+ case '3' : pBuffer[start] = 'B'; break;
+ case '4' : pBuffer[start] = 'C'; break;
+ case '5' : pBuffer[start] = 'D'; break;
+ case '6' : pBuffer[start] = 'E'; break;
+ case '7' : pBuffer[start] = 'F'; break;
+ default: OSL_FAIL("Already a signed value");
+ }
+ }
+
+ // report precision
+ return nRet;
+}
+
+sal_Int32
+getValueOf (sal_Int32 nValue, OStringBuffer& pBuffer)
+{
+ sal_Int32 nChar = 0;
+ if (nValue < 0)
+ {
+ pBuffer.append('-');
+ ++nChar;
+ nValue *= -1;
+ }
+ else
+ if (nValue == 0)
+ {
+ pBuffer.append('0');
+ ++nChar;
+ return nChar;
+ }
+
+ char pInvBuffer [32];
+ sal_Int32 nInvChar = 0;
+ while (nValue > 0)
+ {
+ pInvBuffer [nInvChar++] = '0' + nValue % 10;
+ nValue /= 10;
+ }
+ while (nInvChar > 0)
+ {
+ pBuffer.append(pInvBuffer [--nInvChar]);
+ ++nChar;
+ }
+
+ return nChar;
+}
+
+sal_Int32
+appendStr (const char* pSrc, OStringBuffer& pDst)
+{
+ sal_Int32 nBytes = strlen (pSrc);
+ pDst.append(pSrc, nBytes);
+
+ return nBytes;
+}
+
+/*
+ * copy strings to file
+ */
+
+bool
+WritePS (osl::File* pFile, const char* pString)
+{
+ sal_uInt64 nInLength = rtl_str_getLength (pString);
+ sal_uInt64 nOutLength = 0;
+
+ if (nInLength > 0 && pFile)
+ pFile->write (pString, nInLength, nOutLength);
+
+ return nInLength == nOutLength;
+}
+
+bool
+WritePS (osl::File* pFile, const char* pString, sal_uInt64 nInLength)
+{
+ sal_uInt64 nOutLength = 0;
+
+ if (nInLength > 0 && pFile)
+ pFile->write (pString, nInLength, nOutLength);
+
+ return nInLength == nOutLength;
+}
+
+bool
+WritePS (osl::File* pFile, const OString &rString)
+{
+ sal_uInt64 nInLength = rString.getLength();
+ sal_uInt64 nOutLength = 0;
+
+ if (nInLength > 0 && pFile)
+ pFile->write (rString.getStr(), nInLength, nOutLength);
+
+ return nInLength == nOutLength;
+}
+
+bool
+WritePS (osl::File* pFile, std::u16string_view rString)
+{
+ return WritePS (pFile, OUStringToOString(rString, RTL_TEXTENCODING_ASCII_US));
+}
+
+} /* namespace psp */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/psputil.hxx b/vcl/unx/generic/print/psputil.hxx
new file mode 100644
index 000000000..e5ae18071
--- /dev/null
+++ b/vcl/unx/generic/print/psputil.hxx
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <osl/file.hxx>
+
+#include <rtl/math.hxx>
+#include <rtl/ustring.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/string.hxx>
+
+namespace psp {
+
+/*
+ * string convenience routines
+ */
+sal_Int32 getHexValueOf (sal_Int32 nValue, OStringBuffer& pBuffer);
+sal_Int32 getAlignedHexValueOf (sal_Int32 nValue, OStringBuffer& pBuffer);
+sal_Int32 getValueOf (sal_Int32 nValue, OStringBuffer& pBuffer);
+sal_Int32 appendStr (const char* pSrc, OStringBuffer& pDst);
+
+inline void getValueOfDouble( OStringBuffer& pBuffer, double f, int nPrecision = 0)
+{
+ pBuffer.append(rtl::math::doubleToString( f, rtl_math_StringFormat_G, nPrecision, '.', true ));
+}
+
+bool WritePS (osl::File* pFile, const char* pString);
+bool WritePS (osl::File* pFile, const char* pString, sal_uInt64 nInLength);
+bool WritePS (osl::File* pFile, const OString &rString);
+bool WritePS (osl::File* pFile, std::u16string_view rString);
+
+} /* namespace psp */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/text_gfx.cxx b/vcl/unx/generic/print/text_gfx.cxx
new file mode 100644
index 000000000..d847004ed
--- /dev/null
+++ b/vcl/unx/generic/print/text_gfx.cxx
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "psputil.hxx"
+#include "glyphset.hxx"
+
+#include <unx/printergfx.hxx>
+#include <unx/fontmanager.hxx>
+
+using namespace psp ;
+
+/*
+ * implement text handling printer routines,
+ */
+
+void PrinterGfx::SetFont(
+ sal_Int32 nFontID,
+ sal_Int32 nHeight,
+ sal_Int32 nWidth,
+ Degree10 nAngle,
+ bool bVertical,
+ bool bArtItalic,
+ bool bArtBold
+ )
+{
+ // font and encoding will be set by drawText again immediately
+ // before PSShowText
+ mnFontID = nFontID;
+ maVirtualStatus.maFont.clear();
+ maVirtualStatus.maEncoding = RTL_TEXTENCODING_DONTKNOW;
+ maVirtualStatus.mnTextHeight = nHeight;
+ maVirtualStatus.mnTextWidth = nWidth;
+ maVirtualStatus.mbArtItalic = bArtItalic;
+ maVirtualStatus.mbArtBold = bArtBold;
+ mnTextAngle = nAngle;
+ mbTextVertical = bVertical;
+}
+
+void PrinterGfx::drawGlyph(const Point& rPoint,
+ sal_GlyphId aGlyphId)
+{
+
+ // draw the string
+ // search for a glyph set matching the set font
+ bool bGlyphFound = false;
+ for (auto & elem : maPS3Font)
+ if ( (elem.GetFontID() == mnFontID)
+ && (elem.IsVertical() == mbTextVertical))
+ {
+ elem.DrawGlyph (*this, rPoint, aGlyphId);
+ bGlyphFound = true;
+ break;
+ }
+
+ // not found ? create a new one
+ if (!bGlyphFound)
+ {
+ maPS3Font.emplace_back(mnFontID, mbTextVertical);
+ maPS3Font.back().DrawGlyph (*this, rPoint, aGlyphId);
+ }
+}
+
+void PrinterGfx::DrawGlyph(const Point& rPoint,
+ const GlyphItem& rGlyph)
+{
+ // move and rotate the user coordinate system
+ // avoid the gsave/grestore for the simple cases since it allows
+ // reuse of the current font if it hasn't changed
+ Degree10 nCurrentTextAngle = mnTextAngle;
+ Point aPoint( rPoint );
+
+ if (nCurrentTextAngle)
+ {
+ PSGSave ();
+ PSTranslate (rPoint);
+ PSRotate (nCurrentTextAngle);
+ mnTextAngle = 0_deg10;
+ aPoint = Point( 0, 0 );
+ }
+
+ if (mbTextVertical && rGlyph.IsVertical())
+ {
+ sal_Int32 nTextHeight = maVirtualStatus.mnTextHeight;
+ sal_Int32 nTextWidth = maVirtualStatus.mnTextWidth ? maVirtualStatus.mnTextWidth : maVirtualStatus.mnTextHeight;
+ sal_Int32 nAscend = mrFontMgr.getFontAscend( mnFontID );
+ sal_Int32 nDescend = mrFontMgr.getFontDescend( mnFontID );
+
+ nDescend = nDescend * nTextHeight / 1000;
+ nAscend = nAscend * nTextHeight / 1000;
+
+ Point aRotPoint( -nDescend*nTextWidth/nTextHeight, nAscend*nTextWidth/nTextHeight );
+
+ // transform matrix to new individual direction
+ PSGSave ();
+ GraphicsStatus aSaveStatus = maVirtualStatus;
+ // switch font aspect
+ maVirtualStatus.mnTextWidth = nTextHeight;
+ maVirtualStatus.mnTextHeight = nTextWidth;
+ if( aPoint.X() || aPoint.Y() )
+ PSTranslate( aPoint );
+ PSRotate (900_deg10);
+ // draw the rotated glyph
+ drawGlyph(aRotPoint, rGlyph.glyphId());
+
+ // restore previous state
+ maVirtualStatus = aSaveStatus;
+ PSGRestore();
+ }
+ else
+ drawGlyph(aPoint, rGlyph.glyphId());
+
+ // restore the user coordinate system
+ if (nCurrentTextAngle)
+ {
+ PSGRestore ();
+ mnTextAngle = nCurrentTextAngle;
+ }
+}
+
+/*
+ * spool the converted truetype fonts to the page header after the page body is
+ * complete
+ * for Type1 fonts spool additional reencoding vectors that are necessary to access the
+ * whole font
+ */
+
+void
+PrinterGfx::OnEndJob ()
+{
+ maPS3Font.clear();
+}
+
+void
+PrinterGfx::writeResources( osl::File* pFile, std::vector< OString >& rSuppliedFonts )
+{
+ // write glyphsets and reencodings
+ for (auto & PS3Font : maPS3Font)
+ {
+ PS3Font.PSUploadFont (*pFile, *this, mbUploadPS42Fonts, rSuppliedFonts );
+ }
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/printer/configuration/README b/vcl/unx/generic/printer/configuration/README
new file mode 100644
index 000000000..c39237a53
--- /dev/null
+++ b/vcl/unx/generic/printer/configuration/README
@@ -0,0 +1,5 @@
+Contains ppds for use by vcl when not using CUPS
+
+This is used for the print-to-file functionality. These two PPDs
+describe the range of paper sizes and postscript options necessary for
+printing to postscript without a configured printer.
diff --git a/vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS b/vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS
new file mode 100644
index 000000000..177e2c4e0
--- /dev/null
+++ b/vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS
@@ -0,0 +1,582 @@
+*PPD-Adobe: "4.0"
+*%
+*% This file is part of the LibreOffice project.
+*%
+*% This Source Code Form is subject to the terms of the Mozilla Public
+*% License, v. 2.0. If a copy of the MPL was not distributed with this
+*% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*%
+*% This file incorporates work covered by the following license notice:
+*%
+*% Licensed to the Apache Software Foundation (ASF) under one or more
+*% contributor license agreements. See the NOTICE file distributed
+*% with this work for additional information regarding copyright
+*% ownership. The ASF licenses this file to you under the Apache
+*% License, Version 2.0 (the "License")*% you may not use this file
+*% except in compliance with the License. You may obtain a copy of
+*% the License at http://www.apache.org/licenses/LICENSE-2.0 .
+*%
+
+*% The user must print with a PostScript(R) emulator to non PostScript(R)
+*% printers if the system has no specific printer support. This file
+*% allows the user to print to most printers without any modification.
+*% Standard paper sizes and resolutions are defined. There are some
+*% additional definitions for screen or online documents in this file.
+*% To print to a PostScript(R) printer, use the specific PPD file.
+
+*% ===== General =====
+
+*FormatVersion: "4.0"
+*FileVersion: "1.0"
+*LanguageEncoding: ISOLatin1
+*LanguageVersion: English
+*PSVersion: "(1) 1"
+*Product: "(Generic Printer)"
+*ModelName: "Generic Printer"
+*NickName: "Generic Printer"
+*PCFileName: "SGENPRT.PPD"
+
+
+*% ===== Basic Capabilities and Defaults =====
+
+*ColorDevice: True
+*DefaultColorSpace: RGB
+*LanguageLevel: "2"
+*TTRasterizer: Type42
+
+*% --- For None Color or old PostScript(R) printers use following lines ---
+*% *ColorDevice: False
+*% *DefaultColorSpace: Gray
+*% *LanguageLevel: "1"
+
+*FreeVM: "8388608"
+*VariablePaperSize: True
+*FileSystem: False
+*Throughput: "8"
+*Password: "0"
+*ExitServer: "
+ count 0 eq % is the password on the stack?
+ { true }
+ { dup % potential password
+ statusdict /checkpassword get exec not
+ } ifelse
+ { % if no password or not valid
+ (WARNING : Cannot perform the exitserver command.) =
+ (Password supplied is not valid.) =
+ (Please contact the author of this software.) = flush
+ quit
+ } if
+ serverdict /exitserver get exec
+"
+*End
+*Reset: "
+ count 0 eq % is the password on the stack?
+ { true }
+ { dup % potential password
+ statusdict /checkpassword get exec not
+ } ifelse
+ { % if no password or not valid
+ (WARNING : Cannot reset printer.) =
+ (Password supplied is not valid.) =
+ (Please contact the author of this software.) = flush
+ quit
+ } if
+ serverdict /exitserver get exec
+ systemdict /quit get exec
+ (WARNING : Printer Reset Failed.) = flush
+"
+*End
+
+
+*DefaultResolution: 300dpi
+
+*ResScreenFreq 72dpi: "60.0"
+*ResScreenFreq 144dpi: "60.0"
+*ResScreenFreq 300dpi: "60.0"
+*ResScreenFreq 360dpi: "60.0"
+*ResScreenFreq 600dpi: "60.0"
+*ResScreenFreq 720dpi: "60.0"
+*ResScreenFreq 1200dpi: "60.0"
+*ResScreenFreq 1440dpi: "60.0"
+*ResScreenFreq 2400dpi: "60.0"
+*ResScreenAngle 72dpi: "45.0"
+*ResScreenAngle 144dpi: "45.0"
+*ResScreenAngle 300dpi: "45.0"
+*ResScreenAngle 360dpi: "45.0"
+*ResScreenAngle 600dpi: "45.0"
+*ResScreenAngle 720dpi: "45.0"
+*ResScreenAngle 1200dpi: "45.0"
+*ResScreenAngle 1440dpi: "45.0"
+*ResScreenAngle 2400dpi: "45.0"
+
+
+*% ===== Halftone =====
+
+*ContoneOnly: False
+*DefaultHalftoneType: 1
+*ScreenFreq: "60.0"
+*ScreenAngle: "45.0"
+*DefaultScreenProc: Dot
+*ScreenProc Dot: "
+ { abs exch abs 2 copy add 1 gt {1 sub dup mul exch 1 sub
+ dup mul add 1 sub } { dup mul exch dup mul add 1 exch sub }
+ ifelse } bind
+"
+*End
+*ScreenProc Line: "{ exch pop abs neg } bind"
+*ScreenProc Ellipse: "
+ { abs exch abs 2 copy mul exch 4 mul add 3 sub dup 0
+ lt { pop dup mul exch .75 div dup mul add 4 div 1 exch sub }
+ { dup 1 gt { pop 1 exch sub dup mul exch 1 exch sub .75 div
+ dup mul add 4 div 1 sub }
+ { .5 exch sub exch pop exch pop } ifelse } ifelse } bind
+"
+*End
+*ScreenProc Cross: "{ abs exch abs 2 copy gt { exch } if pop neg } bind"
+
+*DefaultTransfer: Null
+*Transfer Null: "{ } bind"
+*Transfer Null.Inverse: "{ 1 exch sub } bind"
+
+
+*% ===== Paper =====
+
+*OpenUI *PageSize: PickOne
+*OrderDependency: 30 AnySetup *PageSize
+*DefaultPageSize: Letter
+*PageSize A0: "<</PageSize [2384 3370] /ImagingBBox null>> setpagedevice"
+*PageSize A1: "<</PageSize [1684 2384] /ImagingBBox null>> setpagedevice"
+*PageSize A2: "<</PageSize [1191 1684] /ImagingBBox null>> setpagedevice"
+*PageSize A3: "<</PageSize [842 1191] /ImagingBBox null>> setpagedevice"
+*PageSize A4: "<</PageSize [595 842] /ImagingBBox null>> setpagedevice"
+*PageSize A5: "<</PageSize [420 595] /ImagingBBox null>> setpagedevice"
+*PageSize A6: "<</PageSize [297 420] /ImagingBBox null>> setpagedevice"
+*PageSize B4: "<</PageSize [728 1032] /ImagingBBox null>> setpagedevice"
+*PageSize B5: "<</PageSize [516 729] /ImagingBBox null>> setpagedevice"
+*PageSize B6: "<</PageSize [363 516] /ImagingBBox null>> setpagedevice"
+*PageSize Legal/US Legal: "<</PageSize [612 1008] /ImagingBBox null>> setpagedevice"
+*PageSize Letter/US Letter: "<</PageSize [612 792] /ImagingBBox null>> setpagedevice"
+*PageSize Executive: "<</PageSize [522 756] /ImagingBBox null>> setpagedevice"
+*PageSize Statement: "<</PageSize [396 612] /ImagingBBox null>> setpagedevice"
+*PageSize Tabloid/US Tabloid: "<</PageSize [792 1224] /ImagingBBox null>> setpagedevice"
+*PageSize Ledger/Ledger Landscape: "<</PageSize [1224 792] /ImagingBBox null>> setpagedevice"
+*PageSize AnsiC/US C: "<</PageSize [1224 1584] /ImagingBBox null>> setpagedevice"
+*PageSize AnsiD/US D: "<</PageSize [1584 2448] /ImagingBBox null>> setpagedevice"
+*PageSize AnsiE/US E: "<</PageSize [2448 3168] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHA/ARCH A: "<</PageSize [648 864] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHB/ARCH B: "<</PageSize [864 1296] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHC/ARCH C: "<</PageSize [1296 1728] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHD/ARCH D: "<</PageSize [1728 2592] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHE/ARCH E: "<</PageSize [2592 3456] /ImagingBBox null>> setpagedevice"
+*PageSize EnvMonarch/Monarch Envelope: "<</PageSize [279 540] /ImagingBBox null>> setpagedevice"
+*PageSize EnvDL/DL Envelope: "<</PageSize [312 624] /ImagingBBox null>> setpagedevice"
+*PageSize EnvC4/C4 Envelope: "<</PageSize [649 918] /ImagingBBox null>> setpagedevice"
+*PageSize EnvC5/C5 Envelope: "<</PageSize [459 649] /ImagingBBox null>> setpagedevice"
+*PageSize EnvC6/C6 Envelope: "<</PageSize [323 459] /ImagingBBox null>> setpagedevice"
+*PageSize Env10/C10 Envelope: "<</PageSize [297 684] /ImagingBBox null>> setpagedevice"
+*PageSize EnvC65/C65 Envelope: "<</PageSize [324 648] /ImagingBBox null>> setpagedevice"
+*PageSize Folio: "<</PageSize [595 935] /ImagingBBox null>> setpagedevice"
+*?PageSize: "
+ save
+ currentpagedevice /PageSize get aload pop
+ 2 copy gt {exch} if
+ (Unknown)
+ 32 dict
+ dup [2384 3370] (A0) put
+ dup [1684 2384] (A1) put
+ dup [1191 1684] (A2) put
+ dup [842 1191] (A3) put
+ dup [595 842] (A4) put
+ dup [420 595] (A5) put
+ dup [297 420] (A6) put
+ dup [728 1032] (B4) put
+ dup [516 729] (B5) put
+ dup [363 516] (B6) put
+ dup [612 1008] (Legal) put
+ dup [612 792] (Letter) put
+ dup [522 756] (Executive) put
+ dup [396 612] (Statement) put
+ dup [792 1224] (Tabloid) put
+ dup [1224 792] (Ledger) put
+ dup [1224 1584] (AnsiC) put
+ dup [1584 2448] (AnsiD) put
+ dup [2448 3168] (AnsiE) put
+ dup [648 864] (ARCHA) put
+ dup [864 1296] (ARCHB) put
+ dup [1296 1728] (ARCHC) put
+ dup [1728 2592] (ARCHD) put
+ dup [2592 3456] (ARCHE) put
+ dup [279 540] (EnvMonarch) put
+ dup [312 624] (EnvDL) put
+ dup [649 918] (EnvC4) put
+ dup [459 649] (EnvC5) put
+ dup [323 459] (EnvC6) put
+ dup [297 684] (Env10) put
+ dup [324 648] (EnvC65) put
+ dup [595 935] (Folio) put
+ { exch aload pop 4 index sub abs 5 le exch
+ 5 index sub abs 5 le and
+ { exch pop exit } { pop } ifelse
+ } bind forall
+ = flush pop pop
+ restore
+"
+*End
+*CloseUI: *PageSize
+
+*OpenUI *PageRegion: PickOne
+*OrderDependency: 40 AnySetup *PageRegion
+*DefaultPageRegion: Letter
+*PageRegion A0: "<</PageSize [2384 3370] /ImagingBBox null>> setpagedevice"
+*PageRegion A1: "<</PageSize [1684 2384] /ImagingBBox null>> setpagedevice"
+*PageRegion A2: "<</PageSize [1191 1684] /ImagingBBox null>> setpagedevice"
+*PageRegion A3: "<</PageSize [842 1191] /ImagingBBox null>> setpagedevice"
+*PageRegion A4: "<</PageSize [595 842] /ImagingBBox null>> setpagedevice"
+*PageRegion A5: "<</PageSize [420 595] /ImagingBBox null>> setpagedevice"
+*PageRegion A6: "<</PageSize [297 420] /ImagingBBox null>> setpagedevice"
+*PageRegion B4: "<</PageSize [728 1032] /ImagingBBox null>> setpagedevice"
+*PageRegion B5: "<</PageSize [516 729] /ImagingBBox null>> setpagedevice"
+*PageRegion B6: "<</PageSize [363 516] /ImagingBBox null>> setpagedevice"
+*PageRegion Legal/US Legal: "<</PageSize [612 1008] /ImagingBBox null>> setpagedevice"
+*PageRegion Letter/US Letter: "<</PageSize [612 792] /ImagingBBox null>> setpagedevice"
+*PageRegion Executive: "<</PageSize [522 756] /ImagingBBox null>> setpagedevice"
+*PageRegion Statement: "<</PageSize [396 612] /ImagingBBox null>> setpagedevice"
+*PageRegion Tabloid/US Tabloid: "<</PageSize [792 1224] /ImagingBBox null>> setpagedevice"
+*PageRegion Ledger/Ledger Landscape: "<</PageSize [1224 792] /ImagingBBox null>> setpagedevice"
+*PageRegion AnsiC/US C: "<</PageSize [1224 1584] /ImagingBBox null>> setpagedevice"
+*PageRegion AnsiD/US D: "<</PageSize [1584 2448] /ImagingBBox null>> setpagedevice"
+*PageRegion AnsiE/US E: "<</PageSize [2448 3168] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHA/ARCH A: "<</PageSize [648 864] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHB/ARCH B: "<</PageSize [864 1296] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHC/ARCH C: "<</PageSize [1296 1728] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHD/ARCH D: "<</PageSize [1728 2592] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHE/ARCH E: "<</PageSize [2592 3456] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvMonarch/Monarch Envelope: "<</PageSize [279 540] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvDL/DL Envelope: "<</PageSize [312 624] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvC4/C4 Envelope: "<</PageSize [649 918] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvC5/C5 Envelope: "<</PageSize [459 649] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvC6/C6 Envelope: "<</PageSize [323 459] /ImagingBBox null>> setpagedevice"
+*PageRegion Env10/C10 Envelope: "<</PageSize [297 684] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvC65/C65 Envelope: "<</PageSize [324 648] /ImagingBBox null>> setpagedevice"
+*PageRegion Folio: "<</PageSize [595 935] /ImagingBBox null>> setpagedevice"
+*CloseUI: *PageRegion
+
+*DefaultImageableArea: Letter
+*ImageableArea A0: "0 0 2384 3370"
+*ImageableArea A1: "0 0 1684 2384"
+*ImageableArea A2: "0 0 1191 1684"
+*ImageableArea A3: "18 18 824 1173"
+*ImageableArea A4: "18 18 577 824"
+*ImageableArea A5: "18 18 402 577"
+*ImageableArea A6: "18 18 279 402"
+*ImageableArea B4: "18 18 710 1014"
+*ImageableArea B5: "18 18 498 711"
+*ImageableArea B6: "18 18 345 498"
+*ImageableArea Legal: "18 18 594 990"
+*ImageableArea Letter: "18 18 594 774"
+*ImageableArea Executive: "18 18 504 738"
+*ImageableArea Statement: "18 18 378 594"
+*ImageableArea Tabloid: "18 18 774 1206"
+*ImageableArea Ledger: "18 18 1206 774"
+*ImageableArea AnsiC: "0 0 1224 1584"
+*ImageableArea AnsiD: "0 0 1584 2448"
+*ImageableArea AnsiE: "0 0 2448 3168"
+*ImageableArea ARCHA: "0 0 648 864"
+*ImageableArea ARCHB: "0 0 864 1296"
+*ImageableArea ARCHC: "0 0 1296 1728"
+*ImageableArea ARCHD: "0 0 1728 2592"
+*ImageableArea ARCHE: "0 0 2592 3456"
+*ImageableArea EnvMonarch: "0 0 279 540"
+*ImageableArea EnvDL: "0 0 312 624"
+*ImageableArea EnvC4: "0 0 649 918"
+*ImageableArea EnvC5: "0 0 459 649"
+*ImageableArea EnvC6: "0 0 323 459"
+*ImageableArea Env10: "0 0 297 684"
+*ImageableArea EnvC65: "0 0 324 648"
+*ImageableArea Folio: "0 0 595 935"
+
+*DefaultPaperDimension: Letter
+*PaperDimension A0: "2384 3370"
+*PaperDimension A1: "1684 2384"
+*PaperDimension A2: "1191 1684"
+*PaperDimension A3: "842 1191"
+*PaperDimension A4: "595 842"
+*PaperDimension A5: "420 595"
+*PaperDimension A6: "297 420"
+*PaperDimension B4: "728 1032"
+*PaperDimension B5: "516 729"
+*PaperDimension B6: "363 516"
+*PaperDimension Legal: "612 1008"
+*PaperDimension Letter: "612 792"
+*PaperDimension Executive: "522 756"
+*PaperDimension Statement: "396 612"
+*PaperDimension Tabloid: "792 1224"
+*PaperDimension Ledger: "1224 792"
+*PaperDimension AnsiC: "1224 1584"
+*PaperDimension AnsiD: "1584 2448"
+*PaperDimension AnsiE: "2448 3168"
+*PaperDimension ARCHA: "648 864"
+*PaperDimension ARCHB: "864 1296"
+*PaperDimension ARCHC: "1296 1728"
+*PaperDimension ARCHD: "1728 2592"
+*PaperDimension ARCHE: "2592 3456"
+*PaperDimension EnvMonarch: "279 540"
+*PaperDimension EnvDL: "312 624"
+*PaperDimension EnvC4: "649 918"
+*PaperDimension EnvC5: "459 649"
+*PaperDimension EnvC6: "323 459"
+*PaperDimension Env10: "297 684"
+*PaperDimension EnvC65: "324 648"
+*PaperDimension Folio: "595 935"
+
+*% ===== Duplex =====
+*OpenUI *Duplex/Duplex: PickOne
+*OrderDependency: 30 AnySetup *Duplex
+*DefaultDuplex: Simplex
+*Duplex Simplex: ""
+*Duplex None/Off: "
+<</Duplex false /Tumble false
+ /Policies << /Duplex 1 /Tumble 1 >>
+>> setpagedevice"
+*Duplex DuplexNoTumble/Long edge:"
+<</Duplex true /Tumble false
+ /Policies << /Duplex 1 /Tumble 1 >>
+>> setpagedevice"
+*Duplex DuplexTumble/Short edge:"
+<</Duplex true /Tumble true
+ /Policies << /Duplex 1 /Tumble 1 >>
+>> setpagedevice"
+*End
+*CloseUI: *Duplex
+
+*% ===== ManualFeed ===
+*OpenUI *ManualFeed/Manual Feed: Boolean
+*OrderDependency: 15 AnySetup *ManualFeed
+*DefaultManualFeed: False
+*ManualFeed False: "
+<< /ManualFeed false /Policies << /ManualFeed 1 >> >> setpagedevice"
+*ManualFeed True: "
+<< /ManualFeed true /Policies << /ManualFeed 1 >> >> setpagedevice"
+*End
+*CloseUI: *ManualFeed
+
+*% ===== Fonts =====
+
+*DefaultFont: Courier
+*Font AvantGarde-Book: Standard "(001.002)" Standard ROM
+*Font AvantGarde-BookOblique: Standard "(001.000)" Standard ROM
+*Font AvantGarde-Demi: Standard "(001.000)" Standard ROM
+*Font AvantGarde-DemiOblique: Standard "(001.000)" Standard ROM
+*Font Bookman-Demi: Standard "(001.000)" Standard ROM
+*Font Bookman-DemiItalic: Standard "(001.000)" Standard ROM
+*Font Bookman-Light: Standard "(001.000)" Standard ROM
+*Font Bookman-LightItalic: Standard "(001.000)" Standard ROM
+*Font Courier: Standard "(001.000)" Standard ROM
+*Font Courier-Bold: Standard "(001.000)" Standard ROM
+*Font Courier-BoldOblique: Standard "(001.000)" Standard ROM
+*Font Courier-Oblique: Standard "(001.000)" Standard ROM
+*Font Helvetica: Standard "(001.000)" Standard ROM
+*Font Helvetica-Bold: Standard "(001.000)" Standard ROM
+*Font Helvetica-BoldOblique: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow-Bold: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow-BoldOblique: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow-Oblique: Standard "(001.000)" Standard ROM
+*Font Helvetica-Oblique: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-Bold: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-BoldItalic: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-Italic: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-Roman: Standard "(001.000)" Standard ROM
+*Font Palatino-Bold: Standard "(001.000)" Standard ROM
+*Font Palatino-BoldItalic: Standard "(001.000)" Standard ROM
+*Font Palatino-Italic: Standard "(001.000)" Standard ROM
+*Font Palatino-Roman: Standard "(001.000)" Standard ROM
+*Font Symbol: Special "(001.001)" Special ROM
+*Font Times-Bold: Standard "(001.000)" Standard ROM
+*Font Times-BoldItalic: Standard "(001.000)" Standard ROM
+*Font Times-Italic: Standard "(001.000)" Standard ROM
+*Font Times-Roman: Standard "(001.000)" Standard ROM
+*Font ZapfChancery-MediumItalic: Standard "(001.000)" Standard ROM
+*Font ZapfDingbats: Special "(001.000)" Special ROM
+*?FontQuery: "
+ save
+ {
+ count 1 gt
+ {
+ exch dup 127 string cvs (/) print print (:) print
+ /Font resourcestatus {pop pop (Yes)} {(No)} ifelse =
+ }
+ { exit } ifelse
+ } bind loop
+ (*) = flush
+ restore
+"
+*End
+
+*?FontList: "
+ save
+ (*) {cvn ==} 128 string /Font resourceforall
+ (*) = flush
+ restore
+"
+*End
+
+
+*% === Printer Messages ===
+
+*Message: "%%[ exitserver: permanent state may be changed ]%%"
+*Message: "%%[ Flushing: rest of job (to end-of-file) will be ignored ]%%"
+*Message: "\FontName\ not found, using Courier"
+
+*% Status (format: %%[ status: <one of these> %%] )
+*Status: "idle"
+*Status: "busy"
+*Status: "waiting"
+*Status: "printing"
+*Status: "PrinterError: timeout, clearing printer"
+*Status: "PrinterError: paper entry misfeed"
+*Status: "PrinterError: warming up"
+*Status: "PrinterError: service call"
+*Status: "PrinterError: no toner cartridge"
+*Status: "PrinterError: no paper tray"
+*Status: "PrinterError: cover open"
+*Status: "PrinterError: resetting printer"
+*Status: "PrinterError: out of paper"
+*Status: "PrinterError: timeout"
+*Status: "PrinterError: manual feed timeout"
+
+*% Input Sources (format: %%[ status: <stat>; source: <one of these>]%% )
+
+*% Printer Error (format: %%[ PrinterError: <one of these>]%%)
+*PrinterError: "timeout, clearing printer"
+*PrinterError: "paper entry misfeed"
+*PrinterError: "warming up"
+*PrinterError: "service call"
+*PrinterError: "no toner cartridge"
+*PrinterError: "no paper tray"
+*PrinterError: "cover open"
+*PrinterError: "resetting printer"
+*PrinterError: "out of paper"
+*PrinterError: "timeout"
+*PrinterError: "manual feed timeout"
+
+
+*% ===== Color Separation =====
+
+*DefaultColorSep: ProcessBlack.60lpi.300dpi/60 lpi / 300 dpi
+*InkName: ProcessBlack/Process Black
+*InkName: CustomColor/Custom Color
+*InkName: ProcessCyan/Process Cyan
+*InkName: ProcessMagenta/Process Magenta
+*InkName: ProcessYellow/Process Yellow
+
+*% --- For 60 lpi / 72 dpi ---
+*ColorSepScreenAngle ProcessBlack.60lpi.72dpi/60 lpi / 72 dpi: "45"
+*ColorSepScreenAngle CustomColor.60lpi.72dpi/60 lpi / 72 dpi: "45"
+*ColorSepScreenAngle ProcessCyan.60lpi.72dpi/60 lpi / 72 dpi: "15"
+*ColorSepScreenAngle ProcessMagenta.60lpi.72dpi/60 lpi / 72 dpi: "75"
+*ColorSepScreenAngle ProcessYellow.60lpi.72dpi/60 lpi / 72 dpi: "0"
+*ColorSepScreenFreq ProcessBlack.60lpi.72dpi/60 lpi / 72 dpi: "60"
+*ColorSepScreenFreq CustomColor.60lpi.72dpi/60 lpi / 72 dpi: "60"
+*ColorSepScreenFreq ProcessCyan.60lpi.72dpi/60 lpi / 72 dpi: "60"
+*ColorSepScreenFreq ProcessMagenta.60lpi.72dpi/60 lpi / 72 dpi: "60"
+*ColorSepScreenFreq ProcessYellow.60lpi.72dpi/60 lpi / 72 dpi: "60"
+
+*% --- For 60 lpi / 144 dpi ---
+*ColorSepScreenAngle ProcessBlack.60lpi.144dpi/60 lpi / 144 dpi: "45"
+*ColorSepScreenAngle CustomColor.60lpi.144dpi/60 lpi / 144 dpi: "45"
+*ColorSepScreenAngle ProcessCyan.60lpi.144dpi/60 lpi / 144 dpi: "15"
+*ColorSepScreenAngle ProcessMagenta.60lpi.144dpi/60 lpi / 144 dpi: "75"
+*ColorSepScreenAngle ProcessYellow.60lpi.144dpi/60 lpi / 144 dpi: "0"
+*ColorSepScreenFreq ProcessBlack.60lpi.144dpi/60 lpi / 144 dpi: "60"
+*ColorSepScreenFreq CustomColor.60lpi.144dpi/60 lpi / 144 dpi: "60"
+*ColorSepScreenFreq ProcessCyan.60lpi.144dpi/60 lpi / 144 dpi: "60"
+*ColorSepScreenFreq ProcessMagenta.60lpi.144dpi/60 lpi / 144 dpi: "60"
+*ColorSepScreenFreq ProcessYellow.60lpi.144dpi/60 lpi / 144 dpi: "60"
+
+*% --- For 60 lpi / 300 dpi ---
+*ColorSepScreenAngle ProcessBlack.60lpi.300dpi/60 lpi / 300 dpi: "45"
+*ColorSepScreenAngle CustomColor.60lpi.300dpi/60 lpi / 300 dpi: "45"
+*ColorSepScreenAngle ProcessCyan.60lpi.300dpi/60 lpi / 300 dpi: "15"
+*ColorSepScreenAngle ProcessMagenta.60lpi.300dpi/60 lpi / 300 dpi: "75"
+*ColorSepScreenAngle ProcessYellow.60lpi.300dpi/60 lpi / 300 dpi: "0"
+*ColorSepScreenFreq ProcessBlack.60lpi.300dpi/60 lpi / 300 dpi: "60"
+*ColorSepScreenFreq CustomColor.60lpi.300dpi/60 lpi / 300 dpi: "60"
+*ColorSepScreenFreq ProcessCyan.60lpi.300dpi/60 lpi / 300 dpi: "60"
+*ColorSepScreenFreq ProcessMagenta.60lpi.300dpi/60 lpi / 300 dpi: "60"
+*ColorSepScreenFreq ProcessYellow.60lpi.300dpi/60 lpi / 300 dpi: "60"
+
+*% --- For 60 lpi / 360 dpi ---
+*ColorSepScreenAngle ProcessBlack.60lpi.360dpi/60 lpi / 360 dpi: "45"
+*ColorSepScreenAngle CustomColor.60lpi.360dpi/60 lpi / 360 dpi: "45"
+*ColorSepScreenAngle ProcessCyan.60lpi.360dpi/60 lpi / 360 dpi: "15"
+*ColorSepScreenAngle ProcessMagenta.60lpi.360dpi/60 lpi / 360 dpi: "75"
+*ColorSepScreenAngle ProcessYellow.60lpi.360dpi/60 lpi / 360 dpi: "0"
+*ColorSepScreenFreq ProcessBlack.60lpi.360dpi/60 lpi / 360 dpi: "60"
+*ColorSepScreenFreq CustomColor.60lpi.360dpi/60 lpi / 360 dpi: "60"
+*ColorSepScreenFreq ProcessCyan.60lpi.360dpi/60 lpi / 360 dpi: "60"
+*ColorSepScreenFreq ProcessMagenta.60lpi.360dpi/60 lpi / 360 dpi: "60"
+*ColorSepScreenFreq ProcessYellow.60lpi.360dpi/60 lpi / 360 dpi: "60"
+
+*% --- For 71 lpi / 600 dpi ---
+*ColorSepScreenAngle ProcessBlack.71lpi.600dpi/71 lpi / 600 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.71lpi.600dpi/71 lpi / 600 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.71lpi.600dpi/71 lpi / 600 dpi: "71.5651"
+*ColorSepScreenAngle ProcessMagenta.71lpi.600dpi/71 lpi / 600 dpi: "18.4349"
+*ColorSepScreenAngle ProcessYellow.71lpi.600dpi/71 lpi / 600 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.71lpi.600dpi/71 lpi / 600 dpi: "70.7107"
+*ColorSepScreenFreq CustomColor.71lpi.600dpi/71 lpi / 600 dpi: "70.7107"
+*ColorSepScreenFreq ProcessCyan.71lpi.600dpi/71 lpi / 600 dpi: "63.2456"
+*ColorSepScreenFreq ProcessMagenta.71lpi.600dpi/71 lpi / 600 dpi: "63.2456"
+*ColorSepScreenFreq ProcessYellow.71lpi.600dpi/71 lpi / 600 dpi: "66.6667"
+
+*% --- For 71 lpi / 720 dpi ---
+*ColorSepScreenAngle ProcessBlack.71lpi.720dpi/71 lpi / 720 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.71lpi.720dpi/71 lpi / 720 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.71lpi.720dpi/71 lpi / 720 dpi: "71.5651"
+*ColorSepScreenAngle ProcessMagenta.71lpi.720dpi/71 lpi / 720 dpi: "18.4349"
+*ColorSepScreenAngle ProcessYellow.71lpi.720dpi/71 lpi / 720 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.71lpi.720dpi/71 lpi / 720 dpi: "70.7107"
+*ColorSepScreenFreq CustomColor.71lpi.720dpi/71 lpi / 720 dpi: "70.7107"
+*ColorSepScreenFreq ProcessCyan.71lpi.720dpi/71 lpi / 720 dpi: "63.2456"
+*ColorSepScreenFreq ProcessMagenta.71lpi.720dpi/71 lpi / 720 dpi: "63.2456"
+*ColorSepScreenFreq ProcessYellow.71lpi.720dpi/71 lpi / 720 dpi: "66.6667"
+
+*% --- For 100 lpi / 1200 dpi ---
+*ColorSepScreenAngle ProcessBlack.100lpi.1200dpi/100 lpi / 1200 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.100lpi.1200dpi/100 lpi / 1200 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.100lpi.1200dpi/100 lpi / 1200 dpi: "15.0"
+*ColorSepScreenAngle ProcessMagenta.100lpi.1200dpi/100 lpi / 1200 dpi: "75.0"
+*ColorSepScreenAngle ProcessYellow.100lpi.1200dpi/100 lpi / 1200 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+*ColorSepScreenFreq CustomColor.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+*ColorSepScreenFreq ProcessCyan.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+*ColorSepScreenFreq ProcessMagenta.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+*ColorSepScreenFreq ProcessYellow.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+
+*% --- For 100 lpi / 1440 dpi ---
+*ColorSepScreenAngle ProcessBlack.100lpi.1440dpi/100 lpi / 1440 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.100lpi.1440dpi/100 lpi / 1440 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.100lpi.1440dpi/100 lpi / 1440 dpi: "15.0"
+*ColorSepScreenAngle ProcessMagenta.100lpi.1440dpi/100 lpi / 1440 dpi: "75.0"
+*ColorSepScreenAngle ProcessYellow.100lpi.1440dpi/100 lpi / 1440 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+*ColorSepScreenFreq CustomColor.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+*ColorSepScreenFreq ProcessCyan.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+*ColorSepScreenFreq ProcessMagenta.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+*ColorSepScreenFreq ProcessYellow.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+
+*% --- For 175 lpi / 2400 dpi ---
+*ColorSepScreenAngle ProcessBlack.175lpi.2400dpi/175 lpi / 2400 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.175lpi.2400dpi/175 lpi / 2400 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.175lpi.2400dpi/175 lpi / 2400 dpi: "15.0"
+*ColorSepScreenAngle ProcessMagenta.175lpi.2400dpi/175 lpi / 2400 dpi: "75.0"
+*ColorSepScreenAngle ProcessYellow.175lpi.2400dpi/175 lpi / 2400 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+*ColorSepScreenFreq CustomColor.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+*ColorSepScreenFreq ProcessCyan.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+*ColorSepScreenFreq ProcessMagenta.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+*ColorSepScreenFreq ProcessYellow.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+
+*% Last Edit Date: March 24 2000
+*% end of PPD file
diff --git a/vcl/unx/generic/printer/configuration/psprint.conf b/vcl/unx/generic/printer/configuration/psprint.conf
new file mode 100644
index 000000000..1b56e084e
--- /dev/null
+++ b/vcl/unx/generic/printer/configuration/psprint.conf
@@ -0,0 +1,99 @@
+;
+; This file is part of the LibreOffice project.
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;
+; This file incorporates work covered by the following license notice:
+;
+; Licensed to the Apache Software Foundation (ASF) under one or more
+; contributor license agreements. See the NOTICE file distributed
+; with this work for additional information regarding copyright
+; ownership. The ASF licenses this file to you under the Apache
+; License, Version 2.0 (the "License"); you may not use this file
+; except in compliance with the License. You may obtain a copy of
+; the License at http://www.apache.org/licenses/LICENSE-2.0 .
+;
+[__Global_Printer_Defaults__]
+; Copies: the default number of copies produced
+; if key is absent the default is 1
+; Copies=1
+
+; Orientation: the default orientation of pages
+; possible Values: Portrait, Landscape
+; if key is absent the default is Portrait
+; Orientation=Portrait
+
+; Scale: the default scaling of output in percent
+; if key is absent the default is 100
+; Scale=100
+
+; MarginAdjust: the default adjustment to driver margins in 1/100 mm
+; MarginAdjust contains corrections for the driver defined margins
+; the values are comma separated
+; the order is: left,right,top,bottom
+; if key is absent the default is 0,0,0,0
+; MarginAdjust=0,0,0,0
+
+; ColorDepth: the default colordepth of the device in bits
+; possible values: 1, 8, 24
+; if key is absent the default is 24
+; ColorDepth=24
+
+; ColorDevice: the default setting whether the device is color capable
+; possible values: 0: driver setting, -1: grey scale, 1: color
+; if key is absent the default is 0
+; ColorDepth=0
+
+; PSLevel: the default setting of the PostScript level of the output
+; possible values: 0: driver setting, 1: level 1, 2: level2
+; if key is absent the default is 0
+; PSLevel=0
+
+; PPD_PageSize: the default page size to use. If a specific printer does
+; not support this page size its default is used instead.
+; possible values: A0, A1, A2, A3, A4, A5, A6, B4, B5, B6,
+; Legal, Letter, Executive, Statement, Tabloid,
+; Ledger, AnsiC, AnsiD, ARCHA, ARCHB, ARCHC,
+; ARCHD, ARCHE, EnvMonarch, EnvC4, EnvC5, EnvC6,
+; Env10, EnvC65, Folio
+; if key is absent the default value is driver specific
+; PPD_PageSize=A4
+
+
+[Generic Printer]
+; for every printer a group with at least the keys
+; "Printer" and "Command" is required
+
+; Printer: contains the base name of the PPD and the Printer name separated by /
+Printer=SGENPRT/Generic Printer
+
+; DefaultPrinter: marks the default printer
+DefaultPrinter=1
+
+; Location: a user readable string that will be shown in the print dialog
+Location=
+
+; Comment: a user readable string that will be shown in the print dialog
+Comment=
+
+; Command: a command line that accepts PostScript as standard input (pipe)
+; note: a shell will be started for the command
+Command=
+
+; QuickCommand: a command line that accepts PostScript as standard input (pipe)
+; this command line will be used instead of the command line given in the
+; "Command" key, if the user presses the direct print button. In this case
+; no print dialog should be shown, neither from the printing application nor
+; from the command line (example "kprinter --nodialog --stdin")
+; note: a shell will be started for the command
+;QuickCommand=
+
+; Features: a string containing additional comma separated properties of a printer
+; currently valid properties:
+; fax for a Fax printer queue
+; pdf=<dir> for a PDF printer where <dir> is the base directory for output files
+; external_dialog to notify that the print command of a printer will show a dialog
+; and therefore the application should not show its own dialog.
+;Features=
diff --git a/vcl/unx/generic/printer/cpdmgr.cxx b/vcl/unx/generic/printer/cpdmgr.cxx
new file mode 100644
index 000000000..e8b22111d
--- /dev/null
+++ b/vcl/unx/generic/printer/cpdmgr.cxx
@@ -0,0 +1,757 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <cstddef>
+#include <unistd.h>
+
+#include <unx/cpdmgr.hxx>
+
+#include <osl/file.h>
+#include <osl/thread.h>
+
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+
+#include <config_dbus.h>
+#include <config_gio.h>
+
+using namespace psp;
+using namespace osl;
+
+#if ENABLE_DBUS && ENABLE_GIO
+// Function to execute when name is acquired on the bus
+void CPDManager::onNameAcquired (GDBusConnection *connection,
+ const gchar *,
+ gpointer user_data)
+{
+ gchar* contents;
+ // Get Interface for introspection
+ if (!g_file_get_contents (FRONTEND_INTERFACE, &contents, nullptr, nullptr))
+ return;
+
+ GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr);
+
+ g_dbus_connection_register_object (connection,
+ "/org/libreoffice/PrintDialog",
+ introspection_data->interfaces[0],
+ nullptr,
+ nullptr, /* user_data */
+ nullptr, /* user_data_free_func */
+ nullptr); /* GError** */
+ g_free(contents);
+ g_dbus_node_info_unref(introspection_data);
+
+ CPDManager* current = static_cast<CPDManager*>(user_data);
+ std::vector<std::pair<std::string, gchar*>> backends = current->getTempBackends();
+ for (auto const& backend : backends)
+ {
+ // Get Interface for introspection
+ if (g_file_get_contents(BACKEND_INTERFACE, &contents, nullptr, nullptr))
+ {
+ introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr);
+ GDBusProxy *proxy = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ introspection_data->interfaces[0],
+ backend.first.c_str(),
+ backend.second,
+ "org.openprinting.PrintBackend",
+ nullptr,
+ nullptr);
+ g_assert (proxy != nullptr);
+ g_dbus_proxy_call(proxy, "ActivateBackend",
+ nullptr,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, nullptr, nullptr, nullptr);
+
+ g_free(contents);
+ g_object_unref(proxy);
+ g_dbus_node_info_unref(introspection_data);
+ }
+ g_free(backend.second);
+ }
+}
+
+void CPDManager::onNameLost (GDBusConnection *,
+ const gchar *name,
+ gpointer)
+{
+ g_message("Name Lost: %s", name);
+}
+
+void CPDManager::printerAdded (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ CPDManager* current = static_cast<CPDManager*>(user_data);
+ GDBusProxy *proxy;
+ proxy = current->getProxy(sender_name);
+ if (proxy == nullptr) {
+ gchar* contents;
+
+ // Get Interface for introspection
+ if (g_file_get_contents ("/usr/share/dbus-1/interfaces/org.openprinting.Backend.xml", &contents, nullptr, nullptr)) {
+ GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr);
+ proxy = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+ introspection_data->interfaces[0],
+ sender_name,
+ object_path,
+ interface_name,
+ nullptr,
+ nullptr);
+
+ g_free(contents);
+ g_dbus_node_info_unref(introspection_data);
+ std::pair<std::string, GDBusProxy *> new_backend (sender_name, proxy);
+ current->addBackend(std::move(new_backend));
+ }
+ }
+ CPDPrinter *pDest = static_cast<CPDPrinter *>(malloc(sizeof(CPDPrinter)));
+ pDest->backend = proxy;
+ g_variant_get (parameters, "(sssssbss)", &(pDest->id), &(pDest->name), &(pDest->info), &(pDest->location), &(pDest->make_and_model), &(pDest->is_accepting_jobs), &(pDest->printer_state), &(pDest->backend_name));
+ std::stringstream printerName;
+ printerName << pDest->name << ", " << pDest->backend_name;
+ std::stringstream uniqueName;
+ uniqueName << pDest->id << ", " << pDest->backend_name;
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OUString aPrinterName = OStringToOUString( printerName.str().c_str(), aEncoding );
+ OUString aUniqueName = OStringToOUString( uniqueName.str().c_str(), aEncoding );
+ current->addNewPrinter(aPrinterName, aUniqueName, pDest);
+}
+
+void CPDManager::printerRemoved (GDBusConnection *,
+ const gchar *,
+ const gchar *,
+ const gchar *,
+ const gchar *,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ // TODO: Remove every data linked to this particular printer.
+ CPDManager* pManager = static_cast<CPDManager*>(user_data);
+ char* id;
+ char* backend_name;
+ g_variant_get (parameters, "(ss)", &id, &backend_name);
+ std::stringstream uniqueName;
+ uniqueName << id << ", " << backend_name;
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OUString aUniqueName = OStringToOUString( uniqueName.str().c_str(), aEncoding );
+ std::unordered_map<OUString, CPDPrinter *>::iterator it = pManager->m_aCPDDestMap.find( aUniqueName );
+ if (it == pManager->m_aCPDDestMap.end()) {
+ SAL_WARN("vcl.unx.print", "CPD trying to remove non-existent printer from list");
+ return;
+ }
+ pManager->m_aCPDDestMap.erase(it);
+ std::unordered_map<OUString, Printer>::iterator printersIt = pManager->m_aPrinters.find( aUniqueName );
+ if (printersIt == pManager->m_aPrinters.end()) {
+ SAL_WARN("vcl.unx.print", "CPD trying to remove non-existent printer from m_aPrinters");
+ return;
+ }
+ pManager->m_aPrinters.erase(printersIt);
+}
+
+GDBusProxy* CPDManager::getProxy(const std::string& target)
+{
+ std::unordered_map<std::string, GDBusProxy *>::const_iterator it = m_pBackends.find(target);
+ if (it == m_pBackends.end()) {
+ return nullptr;
+ }
+ return it->second;
+}
+
+void CPDManager::addBackend(std::pair<std::string, GDBusProxy *> pair) {
+ m_pBackends.insert(pair);
+}
+
+void CPDManager::addTempBackend(const std::pair<std::string, gchar*>& pair)
+{
+ m_tBackends.push_back(pair);
+}
+
+std::vector<std::pair<std::string, gchar*>> const & CPDManager::getTempBackends() const {
+ return m_tBackends;
+}
+
+void CPDManager::addNewPrinter(const OUString& aPrinterName, const OUString& aUniqueName, CPDPrinter *pDest) {
+ m_aCPDDestMap[aUniqueName] = pDest;
+ bool bSetToGlobalDefaults = m_aPrinters.find( aUniqueName ) == m_aPrinters.end();
+ Printer aPrinter = m_aPrinters[ aUniqueName ];
+ if( bSetToGlobalDefaults )
+ aPrinter.m_aInfo = m_aGlobalDefaults;
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+
+ // TODO: I don't know how this should work when we have multiple
+ // sources with multiple possible defaults for each
+ // if( pDest->is_default )
+ // m_aDefaultPrinter = aPrinterName;
+
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ aPrinter.m_aInfo.m_aComment = OStringToOUString(pDest->info, aEncoding);
+ aPrinter.m_aInfo.m_aLocation = OStringToOUString(pDest->location, aEncoding);
+ // note: the parser that goes with the PrinterInfo
+ // is created implicitly by the JobData::operator=()
+ // when it detects the NULL ptr m_pParser.
+ // if we wanted to fill in the parser here this
+ // would mean we'd have to send a dbus message for each and
+ // every printer - which would be really bad runtime
+ // behaviour
+ aPrinter.m_aInfo.m_pParser = nullptr;
+ aPrinter.m_aInfo.m_aContext.setParser( nullptr );
+ std::unordered_map< OUString, PPDContext >::const_iterator c_it = m_aDefaultContexts.find( aUniqueName );
+ if( c_it != m_aDefaultContexts.end() )
+ {
+ aPrinter.m_aInfo.m_pParser = c_it->second.getParser();
+ aPrinter.m_aInfo.m_aContext = c_it->second;
+ }
+ aPrinter.m_aInfo.setDefaultBackend(true);
+ aPrinter.m_aInfo.m_aDriverName = "CPD:" + aUniqueName;
+ m_aPrinters[ aUniqueName ] = aPrinter;
+}
+#endif
+
+/*
+ * CPDManager class
+ */
+
+CPDManager* CPDManager::tryLoadCPD()
+{
+ CPDManager* pManager = nullptr;
+#if ENABLE_DBUS && ENABLE_GIO
+ static const char* pEnv = getenv("SAL_DISABLE_CPD");
+
+ if (!pEnv || !*pEnv) {
+ // interface description XML files are needed in 'onNameAcquired()'
+ if (!g_file_test(FRONTEND_INTERFACE, G_FILE_TEST_IS_REGULAR) ||
+ !g_file_test(BACKEND_INTERFACE, G_FILE_TEST_IS_REGULAR)) {
+ return nullptr;
+ }
+
+ GDir *dir;
+ const gchar *filename;
+ dir = g_dir_open(BACKEND_DIR, 0, nullptr);
+ if (dir != nullptr) {
+ while ((filename = g_dir_read_name(dir))) {
+ if (pManager == nullptr) {
+ pManager = new CPDManager();
+ }
+ gchar* contents;
+ std::stringstream filepath;
+ filepath << BACKEND_DIR << '/' << filename;
+ if (g_file_get_contents(filepath.str().c_str(), &contents, nullptr, nullptr))
+ {
+ std::pair<std::string, gchar*> new_tbackend (filename, contents);
+ pManager->addTempBackend(new_tbackend);
+ }
+ }
+ g_dir_close(dir);
+ }
+ }
+#endif
+ return pManager;
+}
+
+CPDManager::CPDManager() :
+ PrinterInfoManager( PrinterInfoManager::Type::CPD )
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ // Get Destinations number and pointers
+ GError *error = nullptr;
+ m_pConnection = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, &error);
+ g_assert_no_error (error);
+#endif
+}
+
+CPDManager::~CPDManager()
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ g_dbus_connection_emit_signal (m_pConnection,
+ nullptr,
+ "/org/libreoffice/PrintDialog",
+ "org.openprinting.PrintFrontend",
+ "StopListing",
+ nullptr,
+ nullptr);
+ g_dbus_connection_flush_sync (m_pConnection,
+ nullptr,
+ nullptr);
+ g_dbus_connection_close_sync (m_pConnection,
+ nullptr,
+ nullptr);
+ for (auto const& backend : m_pBackends)
+ {
+ g_object_unref(backend.second);
+ }
+ for (auto const& backend : m_aCPDDestMap)
+ {
+ free(backend.second);
+ }
+#endif
+}
+
+
+const PPDParser* CPDManager::createCPDParser( const OUString& rPrinter )
+{
+ const PPDParser* pNewParser = nullptr;
+#if ENABLE_DBUS && ENABLE_GIO
+ OUString aPrinter;
+
+ if( rPrinter.startsWith("CPD:") )
+ aPrinter = rPrinter.copy( 4 );
+ else
+ aPrinter = rPrinter;
+
+ std::unordered_map< OUString, CPDPrinter * >::iterator dest_it =
+ m_aCPDDestMap.find( aPrinter );
+
+ if( dest_it != m_aCPDDestMap.end() )
+ {
+ CPDPrinter* pDest = dest_it->second;
+ GVariant* ret = nullptr;
+ GError* error = nullptr;
+ ret = g_dbus_proxy_call_sync (pDest->backend, "GetAllOptions",
+ g_variant_new("(s)", (pDest->id)),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, nullptr, &error);
+ if (ret != nullptr && error == nullptr)
+ {
+ // TODO: These keys need to be redefined to preserve usage across libreoffice
+ // InputSlot - media-col.media-source?
+ // Font - not needed now as it is required only for ps and we are using pdf
+ // Dial? - for FAX (need to look up PWG spec)
+
+ int num_attribute;
+ GVariantIter *iter_attr, *iter_supported_values;
+ g_variant_get (ret, "(ia(ssia(s)))", &num_attribute, &iter_attr);
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ PPDKey *pKey = nullptr;
+ OUString aValueName;
+ PPDValue* pValue;
+ std::vector<PPDKey*> keys;
+ std::vector<OUString> default_values;
+ for (int i = 0; i < num_attribute; i++) {
+ char *name, *default_value;
+ int num_supported_values;
+ g_variant_iter_loop(iter_attr, "(ssia(s))",
+ &name, &default_value,
+ &num_supported_values, &iter_supported_values);
+ OUString aOptionName = OStringToOUString( name, aEncoding );
+ OUString aDefaultValue = OStringToOUString( default_value, aEncoding );
+ if (aOptionName == "sides") {
+ // Duplex key is used throughout for checking Duplex Support
+ aOptionName = OUString("Duplex");
+ } else if (aOptionName == "printer-resolution") {
+ // Resolution key is used in places
+ aOptionName = OUString("Resolution");
+ } else if (aOptionName == "media") {
+ // PageSize key is used in many places
+ aOptionName = OUString("PageSize");
+ }
+ default_values.push_back(aDefaultValue);
+ pKey = new PPDKey( aOptionName );
+
+ // If number of values are 0, this is not settable via UI
+ if (num_supported_values > 0 && aDefaultValue != "NA")
+ pKey->m_bUIOption = true;
+
+ bool bDefaultFound = false;
+
+ for (int j = 0; j < num_supported_values; j++) {
+ char* value;
+ g_variant_iter_loop(iter_supported_values, "(s)", &value);
+ aValueName = OStringToOUString( value, aEncoding );
+ if (aOptionName == "Duplex") {
+ // Duplex key matches against very specific Values
+ if (aValueName == "one-sided") {
+ aValueName = OUString("None");
+ } else if (aValueName == "two-sided-long-edge") {
+ aValueName = OUString("DuplexNoTumble");
+ } else if (aValueName == "two-sided-short-edge") {
+ aValueName = OUString("DuplexTumble");
+ }
+ }
+
+ pValue = pKey->insertValue( aValueName, eQuoted );
+ if( ! pValue )
+ continue;
+ pValue->m_aValue = aValueName;
+
+ if (aValueName.equals(aDefaultValue)) {
+ pKey->m_pDefaultValue = pValue;
+ bDefaultFound = true;
+ }
+
+ }
+ // This could be done to ensure default values also appear as options:
+ if (!bDefaultFound && pKey->m_bUIOption) {
+ // pValue = pKey->insertValue( aDefaultValue, eQuoted );
+ // if( pValue )
+ // pValue->m_aValue = aDefaultValue;
+ }
+ keys.emplace_back(pKey);
+ }
+
+ pKey = new PPDKey("ModelName");
+ aValueName = OStringToOUString( "", aEncoding );
+ pValue = pKey->insertValue( aValueName, eQuoted );
+ if( pValue )
+ pValue->m_aValue = aValueName;
+ pKey->m_pDefaultValue = pValue;
+ keys.emplace_back(pKey);
+
+ pKey = new PPDKey("NickName");
+ aValueName = OStringToOUString( pDest->name, aEncoding );
+ pValue = pKey->insertValue( aValueName, eQuoted );
+ if( pValue )
+ pValue->m_aValue = aValueName;
+ pKey->m_pDefaultValue = pValue;
+ keys.emplace_back(pKey);
+
+ pNewParser = new PPDParser(aPrinter, keys);
+ PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
+ PPDContext& rContext = m_aDefaultContexts[ aPrinter ];
+ rContext.setParser( pNewParser );
+ setDefaultPaper( rContext );
+ std::vector<OUString>::iterator defit = default_values.begin();
+ for (auto const& key : keys)
+ {
+ const PPDValue* p1Value = key->getValue( *defit );
+ if( p1Value )
+ {
+ if( p1Value != key->getDefaultValue() )
+ {
+ rContext.setValue( key, p1Value, true );
+ SAL_INFO("vcl.unx.print", "key " << pKey->getKey() << " is set to " << *defit);
+ }
+ else
+ SAL_INFO("vcl.unx.print", "key " << pKey->getKey() << " is defaulted to " << *defit);
+ }
+ ++defit;
+ }
+
+ rInfo.m_pParser = pNewParser;
+ rInfo.m_aContext = rContext;
+ g_variant_unref(ret);
+ }
+ else
+ {
+ g_clear_error(&error);
+ SAL_INFO("vcl.unx.print", "CPD GetAllOptions failed, falling back to generic driver");
+ }
+ }
+ else
+ SAL_INFO("vcl.unx.print", "no dest found for printer " << aPrinter);
+
+ if( ! pNewParser )
+ {
+ // get the default PPD
+ pNewParser = PPDParser::getParser( "SGENPRT" );
+ SAL_WARN("vcl.unx.print", "Parsing default SGENPRT PPD" );
+
+ PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
+
+ rInfo.m_pParser = pNewParser;
+ rInfo.m_aContext.setParser( pNewParser );
+ }
+#else
+ (void)rPrinter;
+#endif
+ return pNewParser;
+}
+
+
+void CPDManager::initialize()
+{
+ // get normal printers, clear printer list
+ PrinterInfoManager::initialize();
+#if ENABLE_DBUS && ENABLE_GIO
+ g_bus_own_name_on_connection (m_pConnection,
+ "org.libreoffice.print-dialog",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ onNameAcquired,
+ onNameLost,
+ this,
+ nullptr);
+
+ g_dbus_connection_signal_subscribe (m_pConnection, // DBus Connection
+ nullptr, // Sender Name
+ "org.openprinting.PrintBackend", // Sender Interface
+ "PrinterAdded", // Signal Name
+ nullptr, // Object Path
+ nullptr, // arg0 behaviour
+ G_DBUS_SIGNAL_FLAGS_NONE, // Signal Flags
+ printerAdded, // Callback Function
+ this,
+ nullptr);
+ g_dbus_connection_signal_subscribe (m_pConnection, // DBus Connection
+ nullptr, // Sender Name
+ "org.openprinting.PrintBackend", // Sender Interface
+ "PrinterRemoved", // Signal Name
+ nullptr, // Object Path
+ nullptr, // arg0 behaviour
+ G_DBUS_SIGNAL_FLAGS_NONE, // Signal Flags
+ printerRemoved, // Callback Function
+ this,
+ nullptr);
+
+ // remove everything that is not a CUPS printer and not
+ // a special purpose printer (PDF, Fax)
+ std::unordered_map< OUString, Printer >::iterator it = m_aPrinters.begin();
+ while (it != m_aPrinters.end())
+ {
+ if( m_aCPDDestMap.find( it->first ) != m_aCPDDestMap.end() )
+ {
+ ++it;
+ continue;
+ }
+
+ if( !it->second.m_aInfo.m_aFeatures.isEmpty() )
+ {
+ ++it;
+ continue;
+ }
+ it = m_aPrinters.erase(it);
+ }
+#endif
+}
+
+void CPDManager::setupJobContextData( JobData& rData )
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ std::unordered_map<OUString, CPDPrinter *>::iterator dest_it =
+ m_aCPDDestMap.find( rData.m_aPrinterName );
+
+ if( dest_it == m_aCPDDestMap.end() )
+ return PrinterInfoManager::setupJobContextData( rData );
+
+ std::unordered_map< OUString, Printer >::iterator p_it =
+ m_aPrinters.find( rData.m_aPrinterName );
+ if( p_it == m_aPrinters.end() ) // huh ?
+ {
+ SAL_WARN("vcl.unx.print", "CPD printer list in disorder, "
+ "no dest for printer " << rData.m_aPrinterName);
+ return;
+ }
+
+ if( p_it->second.m_aInfo.m_pParser == nullptr )
+ {
+ // in turn calls createCPDParser
+ // which updates the printer info
+ p_it->second.m_aInfo.m_pParser = PPDParser::getParser( p_it->second.m_aInfo.m_aDriverName );
+ }
+ if( p_it->second.m_aInfo.m_aContext.getParser() == nullptr )
+ {
+ OUString aPrinter;
+ if( p_it->second.m_aInfo.m_aDriverName.startsWith("CPD:") )
+ aPrinter = p_it->second.m_aInfo.m_aDriverName.copy( 4 );
+ else
+ aPrinter = p_it->second.m_aInfo.m_aDriverName;
+
+ p_it->second.m_aInfo.m_aContext = m_aDefaultContexts[ aPrinter ];
+ }
+
+ rData.m_pParser = p_it->second.m_aInfo.m_pParser;
+ rData.m_aContext = p_it->second.m_aInfo.m_aContext;
+#else
+ (void)rData;
+#endif
+}
+
+FILE* CPDManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ SAL_INFO( "vcl.unx.print", "startSpool: " << rPrintername << " " << (bQuickCommand ? "true" : "false") );
+ if( m_aCPDDestMap.find( rPrintername ) == m_aCPDDestMap.end() )
+ {
+ SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::startSpool" );
+ return PrinterInfoManager::startSpool( rPrintername, bQuickCommand );
+ }
+ OUString aTmpURL, aTmpFile;
+ osl_createTempFile( nullptr, nullptr, &aTmpURL.pData );
+ osl_getSystemPathFromFileURL( aTmpURL.pData, &aTmpFile.pData );
+ OString aSysFile = OUStringToOString( aTmpFile, osl_getThreadTextEncoding() );
+ FILE* fp = fopen( aSysFile.getStr(), "w" );
+ if( fp )
+ m_aSpoolFiles[fp] = aSysFile;
+
+ return fp;
+#else
+ (void)rPrintername;
+ (void)bQuickCommand;
+ return nullptr;
+#endif
+}
+
+#if ENABLE_DBUS && ENABLE_GIO
+void CPDManager::getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, const OString& rJobName, int& rNumOptions, GVariant **arr )
+{
+ GVariantBuilder *builder;
+ builder = g_variant_builder_new(G_VARIANT_TYPE("a(ss)"));
+ g_variant_builder_add(builder, "(ss)", "job-name", rJobName.getStr());
+ if( rJob.m_pParser == rJob.m_aContext.getParser() && rJob.m_pParser ) {
+ std::size_t i;
+ std::size_t nKeys = rJob.m_aContext.countValuesModified();
+ ::std::vector< const PPDKey* > aKeys( nKeys );
+ for( i = 0; i < nKeys; i++ )
+ aKeys[i] = rJob.m_aContext.getModifiedKey( i );
+ for( i = 0; i < nKeys; i++ ) {
+ const PPDKey* pKey = aKeys[i];
+ const PPDValue* pValue = rJob.m_aContext.getValue( pKey );
+ OUString sPayLoad;
+ if (pValue) {
+ sPayLoad = pValue->m_bCustomOption ? pValue->m_aCustomOption : pValue->m_aOption;
+ }
+ if (!sPayLoad.isEmpty()) {
+ OString aKey = OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US );
+ OString aValue = OUStringToOString( sPayLoad, RTL_TEXTENCODING_ASCII_US );
+ if (aKey.equals("Duplex")) {
+ aKey = OString("sides");
+ } else if (aKey.equals("Resolution")) {
+ aKey = OString("printer-resolution");
+ } else if (aKey.equals("PageSize")) {
+ aKey = OString("media");
+ }
+ if (aKey.equals("sides")) {
+ if (aValue.equals("None")) {
+ aValue = OString("one-sided");
+ } else if (aValue.equals("DuplexNoTumble")) {
+ aValue = OString("two-sided-long-edge");
+ } else if (aValue.equals("DuplexTumble")) {
+ aValue = OString("two-sided-short-edge");
+ }
+ }
+ g_variant_builder_add(builder, "(ss)", aKey.getStr(), aValue.getStr());
+ }
+ }
+ }
+ if( rJob.m_nPDFDevice > 0 && rJob.m_nCopies > 1 )
+ {
+ OString aVal( OString::number( rJob.m_nCopies ) );
+ g_variant_builder_add(builder, "(ss)", "copies", aVal.getStr());
+ rNumOptions++;
+ // TODO: something for collate
+ // Maybe this is the equivalent ipp attribute:
+ if (rJob.m_bCollate) {
+ g_variant_builder_add(builder, "(ss)", "multiple-document-handling", "separate-documents-collated-copies");
+ } else {
+ g_variant_builder_add(builder, "(ss)", "multiple-document-handling", "separate-documents-uncollated-copies");
+ }
+ rNumOptions++;
+ }
+ if( ! bBanner )
+ {
+ g_variant_builder_add(builder, "(ss)", "job-sheets", "none");
+ rNumOptions++;
+ }
+ if (rJob.m_eOrientation == orientation::Portrait) {
+ g_variant_builder_add(builder, "(ss)", "orientation-requested", "portrait");
+ rNumOptions++;
+ } else if (rJob.m_eOrientation == orientation::Landscape) {
+ g_variant_builder_add(builder, "(ss)", "orientation-requested", "landscape");
+ rNumOptions++;
+ }
+ (*arr) = g_variant_new("a(ss)", builder);
+ g_variant_builder_unref(builder);
+}
+#endif
+
+bool CPDManager::endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber )
+{
+ bool success = false;
+#if ENABLE_DBUS && ENABLE_GIO
+ SAL_INFO( "vcl.unx.print", "endSpool: " << rPrintername << "," << rJobTitle << " copy count = " << rDocumentJobData.m_nCopies );
+ std::unordered_map< OUString, CPDPrinter * >::iterator dest_it =
+ m_aCPDDestMap.find( rPrintername );
+ if( dest_it == m_aCPDDestMap.end() )
+ {
+ SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::endSpool" );
+ return PrinterInfoManager::endSpool( rPrintername, rJobTitle, pFile, rDocumentJobData, bBanner, rFaxNumber );
+ }
+
+ std::unordered_map< FILE*, OString, FPtrHash >::const_iterator it = m_aSpoolFiles.find( pFile );
+ if( it != m_aSpoolFiles.end() )
+ {
+ fclose( pFile );
+ rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
+ OString sJobName(OUStringToOString(rJobTitle, aEnc));
+ if (!rFaxNumber.isEmpty())
+ {
+ sJobName = OUStringToOString(rFaxNumber, aEnc);
+ }
+ OString aSysFile = it->second;
+ CPDPrinter* pDest = dest_it->second;
+ GVariant* ret;
+ gint job_id;
+ int nNumOptions = 0;
+ GVariant *pArr = nullptr;
+ getOptionsFromDocumentSetup( rDocumentJobData, bBanner, sJobName, nNumOptions, &pArr );
+ ret = g_dbus_proxy_call_sync (pDest->backend, "printFile",
+ g_variant_new(
+ "(ssi@a(ss))",
+ (pDest->id),
+ aSysFile.getStr(),
+ nNumOptions,
+ pArr
+ ),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, nullptr, nullptr);
+ g_variant_get (ret, "(i)", &job_id);
+ if (job_id != -1) {
+ success = true;
+ }
+ g_variant_unref(ret);
+ unlink( it->second.getStr() );
+ m_aSpoolFiles.erase(it);
+ }
+#else
+ (void)rPrintername;
+ (void)rJobTitle;
+ (void)pFile;
+ (void)rDocumentJobData;
+ (void)bBanner;
+ (void)rFaxNumber;
+#endif
+ return success;
+}
+
+bool CPDManager::checkPrintersChanged( bool )
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ bool bChanged = m_aPrintersChanged;
+ m_aPrintersChanged = false;
+ g_dbus_connection_emit_signal (m_pConnection,
+ nullptr,
+ "/org/libreoffice/PrintDialog",
+ "org.openprinting.PrintFrontend",
+ "RefreshBackend",
+ nullptr,
+ nullptr);
+ return bChanged;
+#else
+ return false;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+
diff --git a/vcl/unx/generic/printer/cupsmgr.cxx b/vcl/unx/generic/printer/cupsmgr.cxx
new file mode 100644
index 000000000..6acfe1db6
--- /dev/null
+++ b/vcl/unx/generic/printer/cupsmgr.cxx
@@ -0,0 +1,964 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cups/cups.h>
+#include <cups/http.h>
+#include <cups/ipp.h>
+#include <cups/ppd.h>
+
+#include <unistd.h>
+
+#include <unx/cupsmgr.hxx>
+
+#include <o3tl/string_view.hxx>
+#include <osl/thread.h>
+#include <osl/file.h>
+#include <osl/conditn.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+
+#include <officecfg/Office/Common.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/window.hxx>
+
+#include <algorithm>
+#include <cstddef>
+#include <string_view>
+
+using namespace psp;
+using namespace osl;
+
+namespace {
+
+struct GetPPDAttribs
+{
+ osl::Condition m_aCondition;
+ OString m_aParameter;
+ OString m_aResult;
+ int m_nRefs;
+ bool* m_pResetRunning;
+ osl::Mutex* m_pSyncMutex;
+
+ GetPPDAttribs( const char * m_pParameter,
+ bool* pResetRunning, osl::Mutex* pSyncMutex )
+ : m_aParameter( m_pParameter ),
+ m_pResetRunning( pResetRunning ),
+ m_pSyncMutex( pSyncMutex )
+ {
+ m_nRefs = 2;
+ m_aCondition.reset();
+ }
+
+ ~GetPPDAttribs()
+ {
+ if( !m_aResult.isEmpty() )
+ unlink( m_aResult.getStr() );
+ }
+
+ void unref()
+ {
+ if( --m_nRefs == 0 )
+ {
+ *m_pResetRunning = false;
+ delete this;
+ }
+ }
+
+ void executeCall()
+ {
+ // This CUPS method is not at all thread-safe we need
+ // to dup the pointer to a static buffer it returns ASAP
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ const char* pResult = cupsGetPPD(m_aParameter.getStr());
+ OString aResult = pResult ? OString(pResult) : OString();
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+ MutexGuard aGuard( *m_pSyncMutex );
+ m_aResult = aResult;
+ m_aCondition.set();
+ unref();
+ }
+
+ OString waitResult( TimeValue const *pDelay )
+ {
+ m_pSyncMutex->release();
+
+ if (m_aCondition.wait( pDelay ) != Condition::result_ok
+ )
+ {
+ SAL_WARN("vcl.unx.print",
+ "cupsGetPPD " << m_aParameter << " timed out");
+ }
+ m_pSyncMutex->acquire();
+
+ OString aRetval = m_aResult;
+ m_aResult.clear();
+ unref();
+
+ return aRetval;
+ }
+};
+
+}
+
+extern "C" {
+ static void getPPDWorker(void* pData)
+ {
+ osl_setThreadName("CUPSManager getPPDWorker");
+ GetPPDAttribs* pAttribs = static_cast<GetPPDAttribs*>(pData);
+ pAttribs->executeCall();
+ }
+}
+
+OString CUPSManager::threadedCupsGetPPD( const char* pPrinter )
+{
+ OString aResult;
+
+ m_aGetPPDMutex.acquire();
+ // if one thread hangs in cupsGetPPD already, don't start another
+ if( ! m_bPPDThreadRunning )
+ {
+ m_bPPDThreadRunning = true;
+ GetPPDAttribs* pAttribs = new GetPPDAttribs( pPrinter,
+ &m_bPPDThreadRunning,
+ &m_aGetPPDMutex );
+
+ oslThread aThread = osl_createThread( getPPDWorker, pAttribs );
+
+ TimeValue aValue;
+ aValue.Seconds = 5;
+ aValue.Nanosec = 0;
+
+ // NOTE: waitResult release and acquires the GetPPD mutex
+ aResult = pAttribs->waitResult( &aValue );
+ osl_destroyThread( aThread );
+ }
+ m_aGetPPDMutex.release();
+
+ return aResult;
+}
+
+static const char* setPasswordCallback( const char* /*pIn*/ )
+{
+ const char* pRet = nullptr;
+
+ PrinterInfoManager& rMgr = PrinterInfoManager::get();
+ if( rMgr.getType() == PrinterInfoManager::Type::CUPS ) // sanity check
+ pRet = static_cast<CUPSManager&>(rMgr).authenticateUser();
+ return pRet;
+}
+
+/*
+ * CUPSManager class
+ */
+
+CUPSManager* CUPSManager::tryLoadCUPS()
+{
+ CUPSManager* pManager = nullptr;
+ static const char* pEnv = getenv("SAL_DISABLE_CUPS");
+
+ if (!pEnv || !*pEnv)
+ pManager = new CUPSManager();
+ return pManager;
+}
+
+extern "C"
+{
+static void run_dest_thread_stub( void* pThis )
+{
+ osl_setThreadName("CUPSManager cupsGetDests");
+ CUPSManager::runDestThread( pThis );
+}
+}
+
+CUPSManager::CUPSManager() :
+ PrinterInfoManager( PrinterInfoManager::Type::CUPS ),
+ m_nDests( 0 ),
+ m_pDests( nullptr ),
+ m_bNewDests( false ),
+ m_bPPDThreadRunning( false )
+{
+ m_aDestThread = osl_createThread( run_dest_thread_stub, this );
+}
+
+CUPSManager::~CUPSManager()
+{
+ if( m_aDestThread )
+ {
+ osl_joinWithThread( m_aDestThread );
+ osl_destroyThread( m_aDestThread );
+ }
+
+ if (m_nDests && m_pDests)
+ cupsFreeDests( m_nDests, static_cast<cups_dest_t*>(m_pDests) );
+}
+
+void CUPSManager::runDestThread( void* pThis )
+{
+ static_cast<CUPSManager*>(pThis)->runDests();
+}
+
+void CUPSManager::runDests()
+{
+ SAL_INFO("vcl.unx.print", "starting cupsGetDests");
+ cups_dest_t* pDests = nullptr;
+
+ // n#722902 - do a fast-failing check for cups working *at all* first
+ http_t* p_http;
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ if( (p_http=httpConnectEncrypt(
+ cupsServer(),
+ ippPort(),
+ cupsEncryption())) == nullptr )
+ return;
+
+ int nDests = cupsGetDests2(p_http, &pDests);
+ SAL_INFO("vcl.unx.print", "came out of cupsGetDests");
+
+ osl::MutexGuard aGuard( m_aCUPSMutex );
+ m_nDests = nDests;
+ m_pDests = pDests;
+ m_bNewDests = true;
+ SAL_INFO("vcl.unx.print", "finished cupsGetDests");
+
+ httpClose(p_http);
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+}
+
+void CUPSManager::initialize()
+{
+ // get normal printers, clear printer list
+ PrinterInfoManager::initialize();
+
+ // check whether thread has completed
+ // if not behave like old printing system
+ osl::MutexGuard aGuard( m_aCUPSMutex );
+
+ if( ! m_bNewDests )
+ return;
+
+ // dest thread has run, clean up
+ if( m_aDestThread )
+ {
+ osl_joinWithThread( m_aDestThread );
+ osl_destroyThread( m_aDestThread );
+ m_aDestThread = nullptr;
+ }
+ m_bNewDests = false;
+
+ // clear old stuff
+ m_aCUPSDestMap.clear();
+
+ if( ! (m_nDests && m_pDests ) )
+ return;
+
+ // check for CUPS server(?) > 1.2
+ // since there is no API to query, check for options that were
+ // introduced in dests with 1.2
+ // this is needed to check for %%IncludeFeature support
+ // (#i65684#, #i65491#)
+ bool bUsePDF = false;
+ cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests);
+ const char* pOpt = cupsGetOption( "printer-info",
+ pDest->num_options,
+ pDest->options );
+ if( pOpt )
+ {
+ m_bUseIncludeFeature = true;
+ bUsePDF = officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::get();
+ }
+
+ m_aGlobalDefaults.setDefaultBackend(bUsePDF);
+
+ // do not send include JobPatch; CUPS will insert that itself
+ // TODO: currently unknown which versions of CUPS insert JobPatches
+ // so currently it is assumed CUPS = don't insert JobPatch files
+ m_bUseJobPatch = false;
+
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ int nPrinter = m_nDests;
+
+ // reset global default PPD options; these are queried on demand from CUPS
+ m_aGlobalDefaults.m_pParser = nullptr;
+ m_aGlobalDefaults.m_aContext = PPDContext();
+
+ // add CUPS printers, should there be a printer
+ // with the same name as a CUPS printer, overwrite it
+ while( nPrinter-- )
+ {
+ pDest = static_cast<cups_dest_t*>(m_pDests)+nPrinter;
+ OUString aPrinterName = OStringToOUString( pDest->name, aEncoding );
+ if( pDest->instance && *pDest->instance )
+ {
+ aPrinterName += "/" +
+ OStringToOUString( pDest->instance, aEncoding );
+ }
+
+ // initialize printer with possible configuration from psprint.conf
+ bool bSetToGlobalDefaults = m_aPrinters.find( aPrinterName ) == m_aPrinters.end();
+ Printer aPrinter = m_aPrinters[ aPrinterName ];
+ if( bSetToGlobalDefaults )
+ aPrinter.m_aInfo = m_aGlobalDefaults;
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+ if( pDest->is_default )
+ m_aDefaultPrinter = aPrinterName;
+
+ for( int k = 0; k < pDest->num_options; k++ )
+ {
+ if(!strcmp(pDest->options[k].name, "printer-info"))
+ aPrinter.m_aInfo.m_aComment=OStringToOUString(pDest->options[k].value, aEncoding);
+ if(!strcmp(pDest->options[k].name, "printer-location"))
+ aPrinter.m_aInfo.m_aLocation=OStringToOUString(pDest->options[k].value, aEncoding);
+ if(!strcmp(pDest->options[k].name, "auth-info-required"))
+ aPrinter.m_aInfo.m_aAuthInfoRequired=OStringToOUString(pDest->options[k].value, aEncoding);
+ }
+
+ // note: the parser that goes with the PrinterInfo
+ // is created implicitly by the JobData::operator=()
+ // when it detects the NULL ptr m_pParser.
+ // if we wanted to fill in the parser here this
+ // would mean we'd have to download PPDs for each and
+ // every printer - which would be really bad runtime
+ // behaviour
+ aPrinter.m_aInfo.m_pParser = nullptr;
+ aPrinter.m_aInfo.m_aContext.setParser( nullptr );
+ std::unordered_map< OUString, PPDContext >::const_iterator c_it = m_aDefaultContexts.find( aPrinterName );
+ if( c_it != m_aDefaultContexts.end() )
+ {
+ aPrinter.m_aInfo.m_pParser = c_it->second.getParser();
+ aPrinter.m_aInfo.m_aContext = c_it->second;
+ }
+ aPrinter.m_aInfo.setDefaultBackend(bUsePDF);
+ aPrinter.m_aInfo.m_aDriverName = "CUPS:" + aPrinterName;
+
+ m_aPrinters[ aPrinter.m_aInfo.m_aPrinterName ] = aPrinter;
+ m_aCUPSDestMap[ aPrinter.m_aInfo.m_aPrinterName ] = nPrinter;
+ }
+
+ // remove everything that is not a CUPS printer and not
+ // a special purpose printer (PDF, Fax)
+ std::unordered_map< OUString, Printer >::iterator it = m_aPrinters.begin();
+ while(it != m_aPrinters.end())
+ {
+ if( m_aCUPSDestMap.find( it->first ) != m_aCUPSDestMap.end() )
+ {
+ ++it;
+ continue;
+ }
+
+ if( !it->second.m_aInfo.m_aFeatures.isEmpty() )
+ {
+ ++it;
+ continue;
+ }
+ it = m_aPrinters.erase(it);
+ }
+
+ cupsSetPasswordCB( setPasswordCallback );
+}
+
+static void updatePrinterContextInfo( ppd_group_t* pPPDGroup, PPDContext& rContext )
+{
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ for( int i = 0; i < pPPDGroup->num_options; i++ )
+ {
+ ppd_option_t* pOption = pPPDGroup->options + i;
+ for( int n = 0; n < pOption->num_choices; n++ )
+ {
+ ppd_choice_t* pChoice = pOption->choices + n;
+ if( pChoice->marked )
+ {
+ const PPDKey* pKey = rContext.getParser()->getKey( OStringToOUString( pOption->keyword, aEncoding ) );
+ if( pKey )
+ {
+ const PPDValue* pValue = pKey->getValue( OStringToOUString( pChoice->choice, aEncoding ) );
+ if( pValue )
+ {
+ if( pValue != pKey->getDefaultValue() )
+ {
+ rContext.setValue( pKey, pValue, true );
+ SAL_INFO("vcl.unx.print", "key " << pOption->keyword << " is set to " << pChoice->choice);
+
+ }
+ else
+ SAL_INFO("vcl.unx.print", "key " << pOption->keyword << " is defaulted to " << pChoice->choice);
+ }
+ else
+ SAL_INFO("vcl.unx.print", "caution: value " << pChoice->choice << " not found in key " << pOption->keyword);
+ }
+ else
+ SAL_INFO("vcl.unx.print", "caution: key " << pOption->keyword << " not found in parser");
+ }
+ }
+ }
+
+ // recurse through subgroups
+ for( int g = 0; g < pPPDGroup->num_subgroups; g++ )
+ {
+ updatePrinterContextInfo( pPPDGroup->subgroups + g, rContext );
+ }
+}
+
+const PPDParser* CUPSManager::createCUPSParser( const OUString& rPrinter )
+{
+ const PPDParser* pNewParser = nullptr;
+ OUString aPrinter;
+
+ if( rPrinter.startsWith("CUPS:") )
+ aPrinter = rPrinter.copy( 5 );
+ else
+ aPrinter = rPrinter;
+
+ if( m_aCUPSMutex.tryToAcquire() )
+ {
+ if (m_nDests && m_pDests)
+ {
+ std::unordered_map< OUString, int >::iterator dest_it =
+ m_aCUPSDestMap.find( aPrinter );
+ if( dest_it != m_aCUPSDestMap.end() )
+ {
+ cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests) + dest_it->second;
+ OString aPPDFile = threadedCupsGetPPD( pDest->name );
+ SAL_INFO("vcl.unx.print",
+ "PPD for " << aPrinter << " is " << aPPDFile);
+ if( !aPPDFile.isEmpty() )
+ {
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OUString aFileName( OStringToOUString( aPPDFile, aEncoding ) );
+ // update the printer info with context information
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ ppd_file_t* pPPD = ppdOpenFile( aPPDFile.getStr() );
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+ if( pPPD )
+ {
+ // create the new parser
+ PPDParser* pCUPSParser = new PPDParser( aFileName );
+ pCUPSParser->m_aFile = rPrinter;
+ pNewParser = pCUPSParser;
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ /*int nConflicts =*/ cupsMarkOptions( pPPD, pDest->num_options, pDest->options );
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+ SAL_INFO("vcl.unx.print", "processing the following options for printer " << pDest->name << " (instance " << (pDest->instance == nullptr ? "null" : pDest->instance) << "):");
+ for( int k = 0; k < pDest->num_options; k++ )
+ SAL_INFO("vcl.unx.print",
+ " \"" << pDest->options[k].name <<
+ "\" = \"" << pDest->options[k].value << "\"");
+ PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
+
+ // remember the default context for later use
+ PPDContext& rContext = m_aDefaultContexts[ aPrinter ];
+ rContext.setParser( pNewParser );
+ // set system default paper; printer CUPS PPD options
+ // may overwrite it
+ setDefaultPaper( rContext );
+ for( int i = 0; i < pPPD->num_groups; i++ )
+ updatePrinterContextInfo( pPPD->groups + i, rContext );
+
+ rInfo.m_pParser = pNewParser;
+ rInfo.m_aContext = rContext;
+
+ // clean up the mess
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ ppdClose( pPPD );
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+ }
+ else
+ SAL_INFO("vcl.unx.print", "ppdOpenFile failed, falling back to generic driver");
+
+ // remove temporary PPD file
+ if (!getenv("SAL_CUPS_PPD_RETAIN_TMP"))
+ unlink( aPPDFile.getStr() );
+ }
+ else
+ SAL_INFO("vcl.unx.print", "cupsGetPPD failed, falling back to generic driver");
+ }
+ else
+ SAL_INFO("vcl.unx.print", "no dest found for printer " << aPrinter);
+ }
+ m_aCUPSMutex.release();
+ }
+ else
+ SAL_WARN("vcl.unx.print", "could not acquire CUPS mutex !!!" );
+
+ if( ! pNewParser )
+ {
+ // get the default PPD
+ pNewParser = PPDParser::getParser( "SGENPRT" );
+ SAL_INFO("vcl.unx.print", "Parsing default SGENPRT PPD" );
+
+ PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
+
+ rInfo.m_pParser = pNewParser;
+ rInfo.m_aContext.setParser( pNewParser );
+ }
+
+ return pNewParser;
+}
+
+void CUPSManager::setupJobContextData( JobData& rData )
+{
+ std::unordered_map< OUString, int >::iterator dest_it =
+ m_aCUPSDestMap.find( rData.m_aPrinterName );
+
+ if( dest_it == m_aCUPSDestMap.end() )
+ return PrinterInfoManager::setupJobContextData( rData );
+
+ std::unordered_map< OUString, Printer >::iterator p_it =
+ m_aPrinters.find( rData.m_aPrinterName );
+ if( p_it == m_aPrinters.end() ) // huh ?
+ {
+ SAL_WARN("vcl.unx.print", "CUPS printer list in disorder, "
+ "no dest for printer " << rData.m_aPrinterName);
+ return;
+ }
+
+ if( p_it->second.m_aInfo.m_pParser == nullptr )
+ {
+ // in turn calls createCUPSParser
+ // which updates the printer info
+ p_it->second.m_aInfo.m_pParser = PPDParser::getParser( p_it->second.m_aInfo.m_aDriverName );
+ }
+ if( p_it->second.m_aInfo.m_aContext.getParser() == nullptr )
+ {
+ OUString aPrinter;
+ if( p_it->second.m_aInfo.m_aDriverName.startsWith("CUPS:") )
+ aPrinter = p_it->second.m_aInfo.m_aDriverName.copy( 5 );
+ else
+ aPrinter = p_it->second.m_aInfo.m_aDriverName;
+
+ p_it->second.m_aInfo.m_aContext = m_aDefaultContexts[ aPrinter ];
+ }
+
+ rData.m_pParser = p_it->second.m_aInfo.m_pParser;
+ rData.m_aContext = p_it->second.m_aInfo.m_aContext;
+}
+
+FILE* CUPSManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
+{
+ SAL_INFO( "vcl.unx.print", "startSpool: " << rPrintername << " " << (bQuickCommand ? "true" : "false") );
+
+ if( m_aCUPSDestMap.find( rPrintername ) == m_aCUPSDestMap.end() )
+ {
+ SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::startSpool" );
+ return PrinterInfoManager::startSpool( rPrintername, bQuickCommand );
+ }
+
+ OUString aTmpURL, aTmpFile;
+ osl_createTempFile( nullptr, nullptr, &aTmpURL.pData );
+ osl_getSystemPathFromFileURL( aTmpURL.pData, &aTmpFile.pData );
+ OString aSysFile = OUStringToOString( aTmpFile, osl_getThreadTextEncoding() );
+ FILE* fp = fopen( aSysFile.getStr(), "w" );
+ if( fp )
+ m_aSpoolFiles[fp] = aSysFile;
+
+ return fp;
+}
+
+namespace {
+
+struct less_ppd_key
+{
+ bool operator()(const PPDKey* left, const PPDKey* right)
+ { return left->getOrderDependency() < right->getOrderDependency(); }
+};
+
+}
+
+void CUPSManager::getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, int& rNumOptions, void** rOptions )
+{
+ rNumOptions = 0;
+ *rOptions = nullptr;
+
+ // emit features ordered to OrderDependency
+ // ignore features that are set to default
+
+ // sanity check
+ if( rJob.m_pParser == rJob.m_aContext.getParser() && rJob.m_pParser )
+ {
+ std::size_t i;
+ std::size_t nKeys = rJob.m_aContext.countValuesModified();
+ ::std::vector< const PPDKey* > aKeys( nKeys );
+ for( i = 0; i < nKeys; i++ )
+ aKeys[i] = rJob.m_aContext.getModifiedKey( i );
+ ::std::sort( aKeys.begin(), aKeys.end(), less_ppd_key() );
+
+ for( i = 0; i < nKeys; i++ )
+ {
+ const PPDKey* pKey = aKeys[i];
+ const PPDValue* pValue = rJob.m_aContext.getValue( pKey );
+ OUString sPayLoad;
+ if (pValue && pValue->m_eType == eInvocation)
+ {
+ sPayLoad = pValue->m_bCustomOption ? pValue->m_aCustomOption : pValue->m_aOption;
+ }
+
+ if (!sPayLoad.isEmpty())
+ {
+ OString aKey = OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US );
+ OString aValue = OUStringToOString( sPayLoad, RTL_TEXTENCODING_ASCII_US );
+ rNumOptions = cupsAddOption( aKey.getStr(), aValue.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
+ }
+ }
+ }
+
+ if( rJob.m_nPDFDevice > 0 && rJob.m_nCopies > 1 )
+ {
+ OString aVal( OString::number( rJob.m_nCopies ) );
+ rNumOptions = cupsAddOption( "copies", aVal.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
+ aVal = OString::boolean(rJob.m_bCollate);
+ rNumOptions = cupsAddOption( "collate", aVal.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
+ }
+ if( ! bBanner )
+ {
+ rNumOptions = cupsAddOption( "job-sheets", "none", rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
+ }
+}
+
+namespace
+{
+ class RTSPWDialog : public weld::GenericDialogController
+ {
+ std::unique_ptr<weld::Label> m_xText;
+ std::unique_ptr<weld::Label> m_xDomainLabel;
+ std::unique_ptr<weld::Entry> m_xDomainEdit;
+ std::unique_ptr<weld::Label> m_xUserLabel;
+ std::unique_ptr<weld::Entry> m_xUserEdit;
+ std::unique_ptr<weld::Label> m_xPassLabel;
+ std::unique_ptr<weld::Entry> m_xPassEdit;
+
+ public:
+ RTSPWDialog(weld::Window* pParent, std::string_view rServer, std::string_view rUserName);
+
+ OString getDomain() const
+ {
+ return OUStringToOString( m_xDomainEdit->get_text(), osl_getThreadTextEncoding() );
+ }
+
+ OString getUserName() const
+ {
+ return OUStringToOString( m_xUserEdit->get_text(), osl_getThreadTextEncoding() );
+ }
+
+ OString getPassword() const
+ {
+ return OUStringToOString( m_xPassEdit->get_text(), osl_getThreadTextEncoding() );
+ }
+
+ void SetDomainVisible(bool bShow)
+ {
+ m_xDomainLabel->set_visible(bShow);
+ m_xDomainEdit->set_visible(bShow);
+ }
+
+ void SetUserVisible(bool bShow)
+ {
+ m_xUserLabel->set_visible(bShow);
+ m_xUserEdit->set_visible(bShow);
+ }
+
+ void SetPassVisible(bool bShow)
+ {
+ m_xPassLabel->set_visible(bShow);
+ m_xPassEdit->set_visible(bShow);
+ }
+ };
+
+ RTSPWDialog::RTSPWDialog(weld::Window* pParent, std::string_view rServer, std::string_view rUserName)
+ : GenericDialogController(pParent, "vcl/ui/cupspassworddialog.ui", "CUPSPasswordDialog")
+ , m_xText(m_xBuilder->weld_label("text"))
+ , m_xDomainLabel(m_xBuilder->weld_label("label3"))
+ , m_xDomainEdit(m_xBuilder->weld_entry("domain"))
+ , m_xUserLabel(m_xBuilder->weld_label("label1"))
+ , m_xUserEdit(m_xBuilder->weld_entry("user"))
+ , m_xPassLabel(m_xBuilder->weld_label("label2"))
+ , m_xPassEdit(m_xBuilder->weld_entry("pass"))
+ {
+ OUString aText(m_xText->get_label());
+ aText = aText.replaceFirst("%s", OStringToOUString(rServer, osl_getThreadTextEncoding()));
+ m_xText->set_label(aText);
+ m_xDomainEdit->set_text("WORKGROUP");
+ if (rUserName.empty())
+ m_xUserEdit->grab_focus();
+ else
+ {
+ m_xUserEdit->set_text(OStringToOUString(rUserName, osl_getThreadTextEncoding()));
+ m_xPassEdit->grab_focus();
+ }
+ }
+
+ bool AuthenticateQuery(std::string_view rServer, OString& rUserName, OString& rPassword)
+ {
+ bool bRet = false;
+
+ RTSPWDialog aDialog(Application::GetDefDialogParent(), rServer, rUserName);
+ if (aDialog.run() == RET_OK)
+ {
+ rUserName = aDialog.getUserName();
+ rPassword = aDialog.getPassword();
+ bRet = true;
+ }
+
+ return bRet;
+ }
+}
+
+namespace
+{
+ OString EscapeCupsOption(const OString& rIn)
+ {
+ OStringBuffer sRet;
+ sal_Int32 nLen = rIn.getLength();
+ for (sal_Int32 i = 0; i < nLen; ++i)
+ {
+ switch(rIn[i])
+ {
+ case '\\':
+ case '\'':
+ case '\"':
+ case ',':
+ case ' ':
+ case '\f':
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\v':
+ sRet.append('\\');
+ break;
+ }
+ sRet.append(rIn[i]);
+ }
+ return sRet.makeStringAndClear();
+ }
+}
+
+bool CUPSManager::endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber )
+{
+ SAL_INFO( "vcl.unx.print", "endSpool: " << rPrintername << "," << rJobTitle << " copy count = " << rDocumentJobData.m_nCopies );
+
+ int nJobID = 0;
+
+ osl::MutexGuard aGuard( m_aCUPSMutex );
+
+ std::unordered_map< OUString, int >::iterator dest_it =
+ m_aCUPSDestMap.find( rPrintername );
+ if( dest_it == m_aCUPSDestMap.end() )
+ {
+ SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::endSpool" );
+ return PrinterInfoManager::endSpool( rPrintername, rJobTitle, pFile, rDocumentJobData, bBanner, rFaxNumber );
+ }
+
+ std::unordered_map< FILE*, OString, FPtrHash >::const_iterator it = m_aSpoolFiles.find( pFile );
+ if( it != m_aSpoolFiles.end() )
+ {
+ fclose( pFile );
+ rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
+
+ // setup cups options
+ int nNumOptions = 0;
+ cups_option_t* pOptions = nullptr;
+ auto ppOptions = reinterpret_cast<void**>(&pOptions);
+ getOptionsFromDocumentSetup( rDocumentJobData, bBanner, nNumOptions, ppOptions );
+
+ PrinterInfo aInfo(getPrinterInfo(rPrintername));
+ if (!aInfo.m_aAuthInfoRequired.isEmpty())
+ {
+ bool bDomain(false), bUser(false), bPass(false);
+ sal_Int32 nIndex = 0;
+ do
+ {
+ std::u16string_view aToken = o3tl::getToken(aInfo.m_aAuthInfoRequired, 0, ',', nIndex);
+ if (aToken == u"domain")
+ bDomain = true;
+ else if (aToken == u"username")
+ bUser = true;
+ else if (aToken == u"password")
+ bPass = true;
+ }
+ while (nIndex >= 0);
+
+ if (bDomain || bUser || bPass)
+ {
+ OString sPrinterName(OUStringToOString(rPrintername, RTL_TEXTENCODING_UTF8));
+ OString sUser = cupsUser();
+ RTSPWDialog aDialog(Application::GetDefDialogParent(), sPrinterName, sUser);
+ aDialog.SetDomainVisible(bDomain);
+ aDialog.SetUserVisible(bUser);
+ aDialog.SetPassVisible(bPass);
+
+ if (aDialog.run() == RET_OK)
+ {
+ OString sAuth;
+ if (bDomain)
+ sAuth = EscapeCupsOption(aDialog.getDomain());
+ if (bUser)
+ {
+ if (bDomain)
+ sAuth += ",";
+ sAuth += EscapeCupsOption(aDialog.getUserName());
+ }
+ if (bPass)
+ {
+ if (bUser || bDomain)
+ sAuth += ",";
+ sAuth += EscapeCupsOption(aDialog.getPassword());
+ }
+ nNumOptions = cupsAddOption("auth-info", sAuth.getStr(), nNumOptions, &pOptions);
+ }
+ }
+ }
+
+ OString sJobName(OUStringToOString(rJobTitle, aEnc));
+
+ //fax4CUPS, "the job name will be dialled for you"
+ //so override the jobname with the desired number
+ if (!rFaxNumber.isEmpty())
+ {
+ sJobName = OUStringToOString(rFaxNumber, aEnc);
+ }
+
+ cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests) + dest_it->second;
+ nJobID = cupsPrintFile(pDest->name,
+ it->second.getStr(),
+ sJobName.getStr(),
+ nNumOptions, pOptions);
+ SAL_INFO("vcl.unx.print", "cupsPrintFile( " << pDest->name << ", "
+ << it->second << ", " << rJobTitle << ", " << nNumOptions
+ << ", " << pOptions << " ) returns " << nJobID);
+ for( int n = 0; n < nNumOptions; n++ )
+ SAL_INFO("vcl.unx.print",
+ " option " << pOptions[n].name << "=" << pOptions[n].value);
+#if OSL_DEBUG_LEVEL > 1
+ OString aCmd( "cp " );
+ aCmd += it->second.getStr();
+ aCmd += OString( " $HOME/cupsprint.ps" );
+ system( aCmd.getStr() );
+#endif
+
+ unlink( it->second.getStr() );
+ m_aSpoolFiles.erase(it);
+ if( pOptions )
+ cupsFreeOptions( nNumOptions, pOptions );
+ }
+
+ return nJobID != 0;
+}
+
+bool CUPSManager::checkPrintersChanged( bool bWait )
+{
+ bool bChanged = false;
+ if( bWait )
+ {
+ if( m_aDestThread )
+ {
+ // initial asynchronous detection still running
+ SAL_INFO("vcl.unx.print", "syncing cups discovery thread");
+ osl_joinWithThread( m_aDestThread );
+ osl_destroyThread( m_aDestThread );
+ m_aDestThread = nullptr;
+ SAL_INFO("vcl.unx.print", "done: syncing cups discovery thread");
+ }
+ else
+ {
+ // #i82321# check for cups printer updates
+ // with this change the whole asynchronous detection in a thread is
+ // almost useless. The only relevance left is for some stalled systems
+ // where the user can set SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION
+ // (see vcl/unx/source/gdi/salprnpsp.cxx)
+ // so that checkPrintersChanged( true ) will never be called
+
+ // there is no way to query CUPS whether the printer list has changed
+ // so get the dest list anew
+ if( m_nDests && m_pDests )
+ cupsFreeDests( m_nDests, static_cast<cups_dest_t*>(m_pDests) );
+ m_nDests = 0;
+ m_pDests = nullptr;
+ runDests();
+ }
+ }
+ if( m_aCUPSMutex.tryToAcquire() )
+ {
+ bChanged = m_bNewDests;
+ m_aCUPSMutex.release();
+ }
+
+ if( ! bChanged )
+ {
+ bChanged = PrinterInfoManager::checkPrintersChanged( bWait );
+ // #i54375# ensure new merging with CUPS list in :initialize
+ if( bChanged )
+ m_bNewDests = true;
+ }
+
+ if( bChanged )
+ initialize();
+
+ return bChanged;
+}
+
+const char* CUPSManager::authenticateUser()
+{
+ const char* pRet = nullptr;
+
+ osl::MutexGuard aGuard( m_aCUPSMutex );
+
+ OString aUser = cupsUser();
+ OString aServer = cupsServer();
+ OString aPassword;
+ if (AuthenticateQuery(aServer, aUser, aPassword))
+ {
+ m_aPassword = aPassword;
+ m_aUser = aUser;
+ cupsSetUser( m_aUser.getStr() );
+ pRet = m_aPassword.getStr();
+ }
+
+ return pRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/printer/jobdata.cxx b/vcl/unx/generic/printer/jobdata.cxx
new file mode 100644
index 000000000..9707a8109
--- /dev/null
+++ b/vcl/unx/generic/printer/jobdata.cxx
@@ -0,0 +1,316 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <officecfg/Office/Common.hxx>
+#include <jobdata.hxx>
+#include <printerinfomanager.hxx>
+#include <tools/stream.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <memory>
+
+using namespace psp;
+
+JobData& JobData::operator=(const JobData& rRight)
+{
+ if(this == &rRight)
+ return *this;
+
+ m_nCopies = rRight.m_nCopies;
+ m_bCollate = rRight.m_bCollate;
+ m_nLeftMarginAdjust = rRight.m_nLeftMarginAdjust;
+ m_nRightMarginAdjust = rRight.m_nRightMarginAdjust;
+ m_nTopMarginAdjust = rRight.m_nTopMarginAdjust;
+ m_nBottomMarginAdjust = rRight.m_nBottomMarginAdjust;
+ m_nColorDepth = rRight.m_nColorDepth;
+ m_eOrientation = rRight.m_eOrientation;
+ m_aPrinterName = rRight.m_aPrinterName;
+ m_bPapersizeFromSetup = rRight.m_bPapersizeFromSetup;
+ m_pParser = rRight.m_pParser;
+ m_aContext = rRight.m_aContext;
+ m_nPSLevel = rRight.m_nPSLevel;
+ m_nPDFDevice = rRight.m_nPDFDevice;
+ m_nColorDevice = rRight.m_nColorDevice;
+
+ if( !m_pParser && !m_aPrinterName.isEmpty() )
+ {
+ PrinterInfoManager& rMgr = PrinterInfoManager::get();
+ rMgr.setupJobContextData( *this );
+ }
+ return *this;
+}
+
+void JobData::setCollate( bool bCollate )
+{
+ if (m_nPDFDevice > 0)
+ {
+ m_bCollate = bCollate;
+ return;
+ }
+ const PPDParser* pParser = m_aContext.getParser();
+ if( !pParser )
+ return;
+
+ const PPDKey* pKey = pParser->getKey( "Collate" );
+ if( !pKey )
+ return;
+
+ const PPDValue* pVal = nullptr;
+ if( bCollate )
+ pVal = pKey->getValue( "True" );
+ else
+ {
+ pVal = pKey->getValue( "False" );
+ if( ! pVal )
+ pVal = pKey->getValue( "None" );
+ }
+ m_aContext.setValue( pKey, pVal );
+}
+
+void JobData::setPaper( int i_nWidth, int i_nHeight )
+{
+ if( m_pParser )
+ {
+ OUString aPaper( m_pParser->matchPaper( i_nWidth, i_nHeight ) );
+
+ const PPDKey* pKey = m_pParser->getKey( "PageSize" );
+ const PPDValue* pValue = pKey ? pKey->getValueCaseInsensitive( aPaper ) : nullptr;
+
+ if (pKey && pValue)
+ m_aContext.setValue( pKey, pValue );
+ }
+}
+
+void JobData::setPaperBin( int i_nPaperBin )
+{
+ if( m_pParser )
+ {
+ const PPDKey* pKey = m_pParser->getKey( "InputSlot" );
+ const PPDValue* pValue = pKey ? pKey->getValue( i_nPaperBin ) : nullptr;
+
+ if (pKey && pValue)
+ m_aContext.setValue( pKey, pValue );
+ }
+}
+
+bool JobData::getStreamBuffer( void*& pData, sal_uInt32& bytes )
+{
+ // consistency checks
+ if( ! m_pParser )
+ m_pParser = m_aContext.getParser();
+ if( m_pParser != m_aContext.getParser() ||
+ ! m_pParser )
+ return false;
+
+ SvMemoryStream aStream;
+
+ // write header job data
+ aStream.WriteLine("JobData 1");
+
+ OStringBuffer aLine;
+
+ aLine.append("printer=");
+ aLine.append(OUStringToOString(m_aPrinterName, RTL_TEXTENCODING_UTF8));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("orientation=");
+ if (m_eOrientation == orientation::Landscape)
+ aLine.append("Landscape");
+ else
+ aLine.append("Portrait");
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("copies=");
+ aLine.append(static_cast<sal_Int32>(m_nCopies));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ if (m_nPDFDevice > 0)
+ {
+ aLine.append("collate=");
+ aLine.append(OString::boolean(m_bCollate));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+ }
+
+ aLine.append("marginadjustment=");
+ aLine.append(static_cast<sal_Int32>(m_nLeftMarginAdjust));
+ aLine.append(',');
+ aLine.append(static_cast<sal_Int32>(m_nRightMarginAdjust));
+ aLine.append(',');
+ aLine.append(static_cast<sal_Int32>(m_nTopMarginAdjust));
+ aLine.append(',');
+ aLine.append(static_cast<sal_Int32>(m_nBottomMarginAdjust));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("colordepth=");
+ aLine.append(static_cast<sal_Int32>(m_nColorDepth));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("pslevel=");
+ aLine.append(static_cast<sal_Int32>(m_nPSLevel));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("pdfdevice=");
+ aLine.append(static_cast<sal_Int32>(m_nPDFDevice));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("colordevice=");
+ aLine.append(static_cast<sal_Int32>(m_nColorDevice));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ // now append the PPDContext stream buffer
+ aStream.WriteLine( "PPDContextData" );
+ sal_uLong nBytes;
+ std::unique_ptr<char[]> pContextBuffer(m_aContext.getStreamableBuffer( nBytes ));
+ if( nBytes )
+ aStream.WriteBytes( pContextBuffer.get(), nBytes );
+ pContextBuffer.reset();
+
+ // success
+ bytes = static_cast<sal_uInt32>(aStream.Tell());
+ pData = std::malloc( bytes );
+ memcpy( pData, aStream.GetData(), bytes );
+ return true;
+}
+
+bool JobData::constructFromStreamBuffer( const void* pData, sal_uInt32 bytes, JobData& rJobData )
+{
+ SvMemoryStream aStream( const_cast<void*>(pData), bytes, StreamMode::READ );
+ OString aLine;
+ bool bVersion = false;
+ bool bPrinter = false;
+ bool bOrientation = false;
+ bool bCopies = false;
+ bool bContext = false;
+ bool bMargin = false;
+ bool bColorDepth = false;
+ bool bColorDevice = false;
+ bool bPSLevel = false;
+ bool bPDFDevice = false;
+
+ const char printerEquals[] = "printer=";
+ const char orientatationEquals[] = "orientation=";
+ const char copiesEquals[] = "copies=";
+ const char collateEquals[] = "collate=";
+ const char marginadjustmentEquals[] = "marginadjustment=";
+ const char colordepthEquals[] = "colordepth=";
+ const char colordeviceEquals[] = "colordevice=";
+ const char pslevelEquals[] = "pslevel=";
+ const char pdfdeviceEquals[] = "pdfdevice=";
+
+ while( ! aStream.eof() )
+ {
+ aStream.ReadLine( aLine );
+ if (aLine.startsWith("JobData"))
+ bVersion = true;
+ else if (aLine.startsWith(printerEquals))
+ {
+ bPrinter = true;
+ rJobData.m_aPrinterName = OStringToOUString(aLine.subView(RTL_CONSTASCII_LENGTH(printerEquals)), RTL_TEXTENCODING_UTF8);
+ }
+ else if (aLine.startsWith(orientatationEquals))
+ {
+ bOrientation = true;
+ rJobData.m_eOrientation = o3tl::equalsIgnoreAsciiCase(aLine.subView(RTL_CONSTASCII_LENGTH(orientatationEquals)), "landscape") ? orientation::Landscape : orientation::Portrait;
+ }
+ else if (aLine.startsWith(copiesEquals))
+ {
+ bCopies = true;
+ rJobData.m_nCopies = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(copiesEquals)));
+ }
+ else if (aLine.startsWith(collateEquals))
+ {
+ rJobData.m_bCollate = aLine.copy(RTL_CONSTASCII_LENGTH(collateEquals)).toBoolean();
+ }
+ else if (aLine.startsWith(marginadjustmentEquals))
+ {
+ bMargin = true;
+ sal_Int32 nIdx {RTL_CONSTASCII_LENGTH(marginadjustmentEquals)};
+ rJobData.m_nLeftMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx));
+ rJobData.m_nRightMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx));
+ rJobData.m_nTopMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx));
+ rJobData.m_nBottomMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx));
+ }
+ else if (aLine.startsWith(colordepthEquals))
+ {
+ bColorDepth = true;
+ rJobData.m_nColorDepth = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(colordepthEquals)));
+ }
+ else if (aLine.startsWith(colordeviceEquals))
+ {
+ bColorDevice = true;
+ rJobData.m_nColorDevice = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(colordeviceEquals)));
+ }
+ else if (aLine.startsWith(pslevelEquals))
+ {
+ bPSLevel = true;
+ rJobData.m_nPSLevel = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(pslevelEquals)));
+ }
+ else if (aLine.startsWith(pdfdeviceEquals))
+ {
+ bPDFDevice = true;
+ rJobData.m_nPDFDevice = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(pdfdeviceEquals)));
+ }
+ else if (aLine == "PPDContextData" && bPrinter)
+ {
+ PrinterInfoManager& rManager = PrinterInfoManager::get();
+ const PrinterInfo& rInfo = rManager.getPrinterInfo( rJobData.m_aPrinterName );
+ rJobData.m_pParser = PPDParser::getParser( rInfo.m_aDriverName );
+ if( rJobData.m_pParser )
+ {
+ rJobData.m_aContext.setParser( rJobData.m_pParser );
+ sal_uInt64 nBytes = bytes - aStream.Tell();
+ std::vector<char> aRemain(nBytes+1);
+ nBytes = aStream.ReadBytes(aRemain.data(), nBytes);
+ if (nBytes)
+ {
+ aRemain.resize(nBytes+1);
+ aRemain[nBytes] = 0;
+ rJobData.m_aContext.rebuildFromStreamBuffer(aRemain);
+ bContext = true;
+ }
+ }
+ }
+ }
+
+ return bVersion && bPrinter && bOrientation && bCopies && bContext && bMargin && bPSLevel && bPDFDevice && bColorDevice && bColorDepth;
+}
+
+void JobData::resolveDefaultBackend()
+{
+ if (m_nPSLevel == 0 && m_nPDFDevice == 0)
+ setDefaultBackend(officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::get());
+}
+
+void JobData::setDefaultBackend(bool bUsePDF)
+{
+ if (bUsePDF && m_nPSLevel == 0 && m_nPDFDevice == 0)
+ m_nPDFDevice = 1;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/printer/ppdparser.cxx b/vcl/unx/generic/printer/ppdparser.cxx
new file mode 100644
index 000000000..c16d257e2
--- /dev/null
+++ b/vcl/unx/generic/printer/ppdparser.cxx
@@ -0,0 +1,1991 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <stdlib.h>
+
+#include <comphelper/string.hxx>
+#include <o3tl/string_view.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <ppdparser.hxx>
+#include <strhelper.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+
+#include <unx/helper.hxx>
+#include <unx/cupsmgr.hxx>
+#include <unx/cpdmgr.hxx>
+
+#include <tools/urlobj.hxx>
+#include <tools/stream.hxx>
+#include <tools/zcodec.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <osl/thread.h>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <salhelper/linkhelper.hxx>
+
+#include <com/sun/star/lang/Locale.hpp>
+
+#include <mutex>
+#include <unordered_map>
+
+#ifdef ENABLE_CUPS
+#include <cups/cups.h>
+#endif
+
+#include <config_dbus.h>
+#include <config_gio.h>
+#include <o3tl/hash_combine.hxx>
+
+namespace psp
+{
+ class PPDTranslator
+ {
+ struct LocaleEqual
+ {
+ bool operator()(const css::lang::Locale& i_rLeft,
+ const css::lang::Locale& i_rRight) const
+ {
+ return i_rLeft.Language == i_rRight.Language &&
+ i_rLeft.Country == i_rRight.Country &&
+ i_rLeft.Variant == i_rRight.Variant;
+ }
+ };
+
+ struct LocaleHash
+ {
+ size_t operator()(const css::lang::Locale& rLocale) const
+ {
+ std::size_t seed = 0;
+ o3tl::hash_combine(seed, rLocale.Language.hashCode());
+ o3tl::hash_combine(seed, rLocale.Country.hashCode());
+ o3tl::hash_combine(seed, rLocale.Variant.hashCode());
+ return seed;
+ }
+ };
+
+ typedef std::unordered_map< css::lang::Locale, OUString, LocaleHash, LocaleEqual > translation_map;
+ typedef std::unordered_map< OUString, translation_map > key_translation_map;
+
+ key_translation_map m_aTranslations;
+ public:
+ PPDTranslator() {}
+
+ void insertValue(
+ const OUString& i_rKey,
+ const OUString& i_rOption,
+ const OUString& i_rValue,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale
+ );
+
+ void insertOption( const OUString& i_rKey,
+ const OUString& i_rOption,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale )
+ {
+ insertValue( i_rKey, i_rOption, OUString(), i_rTranslation, i_rLocale );
+ }
+
+ void insertKey( const OUString& i_rKey,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale = css::lang::Locale() )
+ {
+ insertValue( i_rKey, OUString(), OUString(), i_rTranslation, i_rLocale );
+ }
+
+ OUString translateValue(
+ const OUString& i_rKey,
+ const OUString& i_rOption
+ ) const;
+
+ OUString translateOption( const OUString& i_rKey,
+ const OUString& i_rOption ) const
+ {
+ return translateValue( i_rKey, i_rOption );
+ }
+
+ OUString translateKey( const OUString& i_rKey ) const
+ {
+ return translateValue( i_rKey, OUString() );
+ }
+ };
+
+ static css::lang::Locale normalizeInputLocale(
+ const css::lang::Locale& i_rLocale
+ )
+ {
+ css::lang::Locale aLoc( i_rLocale );
+ if( aLoc.Language.isEmpty() )
+ {
+ // empty locale requested, fill in application UI locale
+ aLoc = Application::GetSettings().GetUILanguageTag().getLocale();
+
+ #if OSL_DEBUG_LEVEL > 1
+ static const char* pEnvLocale = getenv( "SAL_PPDPARSER_LOCALE" );
+ if( pEnvLocale && *pEnvLocale )
+ {
+ OString aStr( pEnvLocale );
+ sal_Int32 nLen = aStr.getLength();
+ aLoc.Language = OStringToOUString( aStr.copy( 0, std::min(nLen, 2) ), RTL_TEXTENCODING_MS_1252 );
+ if( nLen >=5 && aStr[2] == '_' )
+ aLoc.Country = OStringToOUString( aStr.copy( 3, 2 ), RTL_TEXTENCODING_MS_1252 );
+ else
+ aLoc.Country.clear();
+ aLoc.Variant.clear();
+ }
+ #endif
+ }
+ /* FIXME-BCP47: using Variant, uppercase? */
+ aLoc.Language = aLoc.Language.toAsciiLowerCase();
+ aLoc.Country = aLoc.Country.toAsciiUpperCase();
+ aLoc.Variant = aLoc.Variant.toAsciiUpperCase();
+
+ return aLoc;
+ }
+
+ void PPDTranslator::insertValue(
+ const OUString& i_rKey,
+ const OUString& i_rOption,
+ const OUString& i_rValue,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale
+ )
+ {
+ OUStringBuffer aKey( i_rKey.getLength() + i_rOption.getLength() + i_rValue.getLength() + 2 );
+ aKey.append( i_rKey );
+ if( !i_rOption.isEmpty() || !i_rValue.isEmpty() )
+ {
+ aKey.append( ':' );
+ aKey.append( i_rOption );
+ }
+ if( !i_rValue.isEmpty() )
+ {
+ aKey.append( ':' );
+ aKey.append( i_rValue );
+ }
+ if( !aKey.isEmpty() && !i_rTranslation.isEmpty() )
+ {
+ OUString aK( aKey.makeStringAndClear() );
+ css::lang::Locale aLoc;
+ /* FIXME-BCP47: using Variant, uppercase? */
+ aLoc.Language = i_rLocale.Language.toAsciiLowerCase();
+ aLoc.Country = i_rLocale.Country.toAsciiUpperCase();
+ aLoc.Variant = i_rLocale.Variant.toAsciiUpperCase();
+ m_aTranslations[ aK ][ aLoc ] = i_rTranslation;
+ }
+ }
+
+ OUString PPDTranslator::translateValue(
+ const OUString& i_rKey,
+ const OUString& i_rOption
+ ) const
+ {
+ OUString aResult;
+
+ OUStringBuffer aKey( i_rKey.getLength() + i_rOption.getLength() + 2 );
+ aKey.append( i_rKey );
+ if( !i_rOption.isEmpty() )
+ {
+ aKey.append( ':' );
+ aKey.append( i_rOption );
+ }
+ if( !aKey.isEmpty() )
+ {
+ OUString aK( aKey.makeStringAndClear() );
+ key_translation_map::const_iterator it = m_aTranslations.find( aK );
+ if( it != m_aTranslations.end() )
+ {
+ const translation_map& rMap( it->second );
+
+ css::lang::Locale aLoc( normalizeInputLocale( css::lang::Locale() ) );
+ /* FIXME-BCP47: use LanguageTag::getFallbackStrings()? */
+ for( int nTry = 0; nTry < 4; nTry++ )
+ {
+ translation_map::const_iterator tr = rMap.find( aLoc );
+ if( tr != rMap.end() )
+ {
+ aResult = tr->second;
+ break;
+ }
+ switch( nTry )
+ {
+ case 0: aLoc.Variant.clear();break;
+ case 1: aLoc.Country.clear();break;
+ case 2: aLoc.Language.clear();break;
+ }
+ }
+ }
+ }
+ return aResult;
+ }
+
+ class PPDCache
+ {
+ public:
+ std::vector< std::unique_ptr<PPDParser> > aAllParsers;
+ std::optional<std::unordered_map< OUString, OUString >> xAllPPDFiles;
+ };
+}
+
+using namespace psp;
+
+namespace
+{
+ PPDCache& getPPDCache()
+ {
+ static PPDCache thePPDCache;
+ return thePPDCache;
+ }
+
+class PPDDecompressStream
+{
+private:
+ PPDDecompressStream(const PPDDecompressStream&) = delete;
+ PPDDecompressStream& operator=(const PPDDecompressStream&) = delete;
+
+ std::unique_ptr<SvFileStream> mpFileStream;
+ std::unique_ptr<SvMemoryStream> mpMemStream;
+ OUString maFileName;
+
+public:
+ explicit PPDDecompressStream( const OUString& rFile );
+ ~PPDDecompressStream();
+
+ bool IsOpen() const;
+ bool eof() const;
+ OString ReadLine();
+ void Open( const OUString& i_rFile );
+ void Close();
+ const OUString& GetFileName() const { return maFileName; }
+};
+
+}
+
+PPDDecompressStream::PPDDecompressStream( const OUString& i_rFile )
+{
+ Open( i_rFile );
+}
+
+PPDDecompressStream::~PPDDecompressStream()
+{
+ Close();
+}
+
+void PPDDecompressStream::Open( const OUString& i_rFile )
+{
+ Close();
+
+ mpFileStream.reset( new SvFileStream( i_rFile, StreamMode::READ ) );
+ maFileName = mpFileStream->GetFileName();
+
+ if( ! mpFileStream->IsOpen() )
+ {
+ Close();
+ return;
+ }
+
+ OString aLine;
+ mpFileStream->ReadLine( aLine );
+ mpFileStream->Seek( 0 );
+
+ // check for compress'ed or gzip'ed file
+ if( aLine.getLength() <= 1 ||
+ static_cast<unsigned char>(aLine[0]) != 0x1f ||
+ static_cast<unsigned char>(aLine[1]) != 0x8b /* check for gzip */ )
+ return;
+
+ // so let's try to decompress the stream
+ mpMemStream.reset( new SvMemoryStream( 4096, 4096 ) );
+ ZCodec aCodec;
+ aCodec.BeginCompression( ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true );
+ tools::Long nComp = aCodec.Decompress( *mpFileStream, *mpMemStream );
+ aCodec.EndCompression();
+ if( nComp < 0 )
+ {
+ // decompression failed, must be an uncompressed stream after all
+ mpMemStream.reset();
+ mpFileStream->Seek( 0 );
+ }
+ else
+ {
+ // compression successful, can get rid of file stream
+ mpFileStream.reset();
+ mpMemStream->Seek( 0 );
+ }
+}
+
+void PPDDecompressStream::Close()
+{
+ mpMemStream.reset();
+ mpFileStream.reset();
+}
+
+bool PPDDecompressStream::IsOpen() const
+{
+ return (mpMemStream || (mpFileStream && mpFileStream->IsOpen()));
+}
+
+bool PPDDecompressStream::eof() const
+{
+ return ( mpMemStream ? mpMemStream->eof() : ( mpFileStream == nullptr || mpFileStream->eof() ) );
+}
+
+OString PPDDecompressStream::ReadLine()
+{
+ OString o_rLine;
+ if( mpMemStream )
+ mpMemStream->ReadLine( o_rLine );
+ else if( mpFileStream )
+ mpFileStream->ReadLine( o_rLine );
+ return o_rLine;
+}
+
+static osl::FileBase::RC resolveLink( const OUString& i_rURL, OUString& o_rResolvedURL, OUString& o_rBaseName, osl::FileStatus::Type& o_rType)
+{
+ salhelper::LinkResolver aResolver(osl_FileStatus_Mask_FileName |
+ osl_FileStatus_Mask_Type |
+ osl_FileStatus_Mask_FileURL);
+
+ osl::FileBase::RC aRet = aResolver.fetchFileStatus(i_rURL, 10/*nLinkLevel*/);
+
+ if (aRet == osl::FileBase::E_None)
+ {
+ o_rResolvedURL = aResolver.m_aStatus.getFileURL();
+ o_rBaseName = aResolver.m_aStatus.getFileName();
+ o_rType = aResolver.m_aStatus.getFileType();
+ }
+
+ return aRet;
+}
+
+void PPDParser::scanPPDDir( const OUString& rDir )
+{
+ static struct suffix_t
+ {
+ const char* pSuffix;
+ const sal_Int32 nSuffixLen;
+ } const pSuffixes[] =
+ { { ".PS", 3 }, { ".PPD", 4 }, { ".PS.GZ", 6 }, { ".PPD.GZ", 7 } };
+
+ PPDCache &rPPDCache = getPPDCache();
+
+ osl::Directory aDir( rDir );
+ if ( aDir.open() != osl::FileBase::E_None )
+ return;
+
+ osl::DirectoryItem aItem;
+
+ INetURLObject aPPDDir(rDir);
+ while( aDir.getNextItem( aItem ) == osl::FileBase::E_None )
+ {
+ osl::FileStatus aStatus( osl_FileStatus_Mask_FileName );
+ if( aItem.getFileStatus( aStatus ) == osl::FileBase::E_None )
+ {
+ OUString aFileURL, aFileName;
+ osl::FileStatus::Type eType = osl::FileStatus::Unknown;
+ OUString aURL = rDir + "/" + aStatus.getFileName();
+
+ if(resolveLink( aURL, aFileURL, aFileName, eType ) == osl::FileBase::E_None)
+ {
+ if( eType == osl::FileStatus::Regular )
+ {
+ INetURLObject aPPDFile = aPPDDir;
+ aPPDFile.Append( aFileName );
+
+ // match extension
+ for(const suffix_t & rSuffix : pSuffixes)
+ {
+ if( aFileName.getLength() > rSuffix.nSuffixLen )
+ {
+ if( aFileName.endsWithIgnoreAsciiCaseAsciiL( rSuffix.pSuffix, rSuffix.nSuffixLen ) )
+ {
+ (*rPPDCache.xAllPPDFiles)[ aFileName.copy( 0, aFileName.getLength() - rSuffix.nSuffixLen ) ] = aPPDFile.PathToFileName();
+ break;
+ }
+ }
+ }
+ }
+ else if( eType == osl::FileStatus::Directory )
+ {
+ scanPPDDir( aFileURL );
+ }
+ }
+ }
+ }
+ aDir.close();
+}
+
+void PPDParser::initPPDFiles(PPDCache &rPPDCache)
+{
+ if( rPPDCache.xAllPPDFiles )
+ return;
+
+ rPPDCache.xAllPPDFiles.emplace();
+
+ // check installation directories
+ std::vector< OUString > aPathList;
+ psp::getPrinterPathList( aPathList, PRINTER_PPDDIR );
+ for (auto const& path : aPathList)
+ {
+ INetURLObject aPPDDir( path, INetProtocol::File, INetURLObject::EncodeMechanism::All );
+ scanPPDDir( aPPDDir.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+ }
+ if( rPPDCache.xAllPPDFiles->find( OUString( "SGENPRT" ) ) != rPPDCache.xAllPPDFiles->end() )
+ return;
+
+ // last try: search in directory of executable (mainly for setup)
+ OUString aExe;
+ if( osl_getExecutableFile( &aExe.pData ) == osl_Process_E_None )
+ {
+ INetURLObject aDir( aExe );
+ aDir.removeSegment();
+ SAL_INFO("vcl.unx.print", "scanning last chance dir: "
+ << aDir.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ scanPPDDir( aDir.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+ SAL_INFO("vcl.unx.print", "SGENPRT "
+ << (rPPDCache.xAllPPDFiles->find("SGENPRT") ==
+ rPPDCache.xAllPPDFiles->end() ? "not found" : "found"));
+ }
+}
+
+OUString PPDParser::getPPDFile( const OUString& rFile )
+{
+ INetURLObject aPPD( rFile, INetProtocol::File, INetURLObject::EncodeMechanism::All );
+ // someone might enter a full qualified name here
+ PPDDecompressStream aStream( aPPD.PathToFileName() );
+ if( ! aStream.IsOpen() )
+ {
+ std::unordered_map< OUString, OUString >::const_iterator it;
+ PPDCache &rPPDCache = getPPDCache();
+
+ bool bRetry = true;
+ do
+ {
+ initPPDFiles(rPPDCache);
+ // some PPD files contain dots beside the extension, so try name first
+ // and cut of points after that
+ OUString aBase( rFile );
+ sal_Int32 nLastIndex = aBase.lastIndexOf( '/' );
+ if( nLastIndex >= 0 )
+ aBase = aBase.copy( nLastIndex+1 );
+ do
+ {
+ it = rPPDCache.xAllPPDFiles->find( aBase );
+ nLastIndex = aBase.lastIndexOf( '.' );
+ if( nLastIndex > 0 )
+ aBase = aBase.copy( 0, nLastIndex );
+ } while( it == rPPDCache.xAllPPDFiles->end() && nLastIndex > 0 );
+
+ if( it == rPPDCache.xAllPPDFiles->end() && bRetry )
+ {
+ // a new file ? rehash
+ rPPDCache.xAllPPDFiles.reset();
+ bRetry = false;
+ // note this is optimized for office start where
+ // no new files occur and initPPDFiles is called only once
+ }
+ } while( ! rPPDCache.xAllPPDFiles );
+
+ if( it != rPPDCache.xAllPPDFiles->end() )
+ aStream.Open( it->second );
+ }
+
+ OUString aRet;
+ if( aStream.IsOpen() )
+ {
+ OString aLine = aStream.ReadLine();
+ if (aLine.startsWith("*PPD-Adobe"))
+ aRet = aStream.GetFileName();
+ else
+ {
+ // our *Include hack does usually not begin
+ // with *PPD-Adobe, so try some lines for *Include
+ int nLines = 10;
+ while (aLine.indexOf("*Include") != 0 && --nLines)
+ aLine = aStream.ReadLine();
+ if( nLines )
+ aRet = aStream.GetFileName();
+ }
+ }
+
+ return aRet;
+}
+
+const PPDParser* PPDParser::getParser( const OUString& rFile )
+{
+ // Recursive because we can get re-entered via CUPSManager::createCUPSParser
+ static std::recursive_mutex aMutex;
+ std::scoped_lock aGuard( aMutex );
+
+ OUString aFile = rFile;
+ if( !rFile.startsWith( "CUPS:" ) && !rFile.startsWith( "CPD:" ) )
+ aFile = getPPDFile( rFile );
+ if( aFile.isEmpty() )
+ {
+ SAL_INFO("vcl.unx.print", "Could not get printer PPD file \""
+ << rFile << "\" !");
+ return nullptr;
+ }
+ else
+ SAL_INFO("vcl.unx.print", "Parsing printer info from \""
+ << rFile << "\" !");
+
+
+ PPDCache &rPPDCache = getPPDCache();
+ for( auto const & i : rPPDCache.aAllParsers )
+ if( i->m_aFile == aFile )
+ return i.get();
+
+ PPDParser* pNewParser = nullptr;
+ if( !aFile.startsWith( "CUPS:" ) && !aFile.startsWith( "CPD:" ) )
+ pNewParser = new PPDParser( aFile );
+ else
+ {
+ PrinterInfoManager& rMgr = PrinterInfoManager::get();
+ if( rMgr.getType() == PrinterInfoManager::Type::CUPS )
+ {
+#ifdef ENABLE_CUPS
+ pNewParser = const_cast<PPDParser*>(static_cast<CUPSManager&>(rMgr).createCUPSParser( aFile ));
+#endif
+ } else if ( rMgr.getType() == PrinterInfoManager::Type::CPD )
+ {
+#if ENABLE_DBUS && ENABLE_GIO
+ pNewParser = const_cast<PPDParser*>(static_cast<CPDManager&>(rMgr).createCPDParser( aFile ));
+#endif
+ }
+ }
+ if( pNewParser )
+ {
+ // this may actually be the SGENPRT parser,
+ // so ensure uniqueness here (but don't remove last we delete us!)
+ if (std::none_of(
+ rPPDCache.aAllParsers.begin(),
+ rPPDCache.aAllParsers.end(),
+ [pNewParser] (std::unique_ptr<PPDParser> const & x) { return x.get() == pNewParser; } ))
+ {
+ // insert new parser to vector
+ rPPDCache.aAllParsers.emplace_back(pNewParser);
+ }
+ }
+ return pNewParser;
+}
+
+PPDParser::PPDParser(const OUString& rFile, const std::vector<PPDKey*>& keys)
+ : m_aFile(rFile)
+ , m_bColorDevice(false)
+ , m_bType42Capable(false)
+ , m_nLanguageLevel(0)
+ , m_aFileEncoding(RTL_TEXTENCODING_MS_1252)
+ , m_pImageableAreas(nullptr)
+ , m_pDefaultPaperDimension(nullptr)
+ , m_pPaperDimensions(nullptr)
+ , m_pDefaultInputSlot(nullptr)
+ , m_pDefaultResolution(nullptr)
+ , m_pTranslator(new PPDTranslator())
+{
+ for (auto & key: keys)
+ {
+ insertKey( std::unique_ptr<PPDKey>(key) );
+ }
+
+ // fill in shortcuts
+ const PPDKey* pKey;
+
+ pKey = getKey( "PageSize" );
+
+ if ( pKey ) {
+ std::unique_ptr<PPDKey> pImageableAreas(new PPDKey("ImageableArea"));
+ std::unique_ptr<PPDKey> pPaperDimensions(new PPDKey("PaperDimension"));
+#if defined(CUPS_VERSION_MAJOR)
+#if (CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR >= 7) || CUPS_VERSION_MAJOR > 1
+ for (int i = 0; i < pKey->countValues(); i++) {
+ const PPDValue* pValue = pKey -> getValue(i);
+ OUString aValueName = pValue -> m_aOption;
+ PPDValue* pImageableAreaValue = pImageableAreas -> insertValue( aValueName, eQuoted );
+ PPDValue* pPaperDimensionValue = pPaperDimensions -> insertValue( aValueName, eQuoted );
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OString o = OUStringToOString( aValueName, aEncoding );
+ pwg_media_t *pPWGMedia = pwgMediaForPWG(o.pData->buffer);
+ if (pPWGMedia != nullptr) {
+ OUStringBuffer aBuf( 256 );
+ aBuf = "0 0 " +
+ OUString::number(PWG_TO_POINTS(pPWGMedia -> width)) +
+ " " +
+ OUString::number(PWG_TO_POINTS(pPWGMedia -> length));
+ if ( pImageableAreaValue )
+ pImageableAreaValue->m_aValue = aBuf.makeStringAndClear();
+ aBuf.append( PWG_TO_POINTS(pPWGMedia -> width) );
+ aBuf.append( " " );
+ aBuf.append( PWG_TO_POINTS(pPWGMedia -> length) );
+ if ( pPaperDimensionValue )
+ pPaperDimensionValue->m_aValue = aBuf.makeStringAndClear();
+ if (aValueName.equals(pKey -> getDefaultValue() -> m_aOption)) {
+ pImageableAreas -> m_pDefaultValue = pImageableAreaValue;
+ pPaperDimensions -> m_pDefaultValue = pPaperDimensionValue;
+ }
+ }
+ }
+#endif // HAVE_CUPS_API_1_7
+#endif
+ insertKey(std::move(pImageableAreas));
+ insertKey(std::move(pPaperDimensions));
+ }
+
+ m_pImageableAreas = getKey( "ImageableArea" );
+ const PPDValue* pDefaultImageableArea = nullptr;
+ if( m_pImageableAreas )
+ pDefaultImageableArea = m_pImageableAreas->getDefaultValue();
+ if (m_pImageableAreas == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no ImageableArea in " << m_aFile);
+ }
+ if (pDefaultImageableArea == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no DefaultImageableArea in " << m_aFile);
+ }
+
+ m_pPaperDimensions = getKey( "PaperDimension" );
+ if( m_pPaperDimensions )
+ m_pDefaultPaperDimension = m_pPaperDimensions->getDefaultValue();
+ if (m_pPaperDimensions == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no PaperDimensions in " << m_aFile);
+ }
+ if (m_pDefaultPaperDimension == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no DefaultPaperDimensions in " << m_aFile);
+ }
+
+ auto pResolutions = getKey( "Resolution" );
+ if( pResolutions )
+ m_pDefaultResolution = pResolutions->getDefaultValue();
+ if (pResolutions == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no Resolution in " << m_aFile);
+ }
+ SAL_INFO_IF(!m_pDefaultResolution, "vcl.unx.print", "no DefaultResolution in " + m_aFile);
+
+ auto pInputSlots = getKey( "InputSlot" );
+ if( pInputSlots )
+ m_pDefaultInputSlot = pInputSlots->getDefaultValue();
+ SAL_INFO_IF(!pInputSlots, "vcl.unx.print", "no InputSlot in " << m_aFile);
+ SAL_INFO_IF(!m_pDefaultInputSlot, "vcl.unx.print", "no DefaultInputSlot in " << m_aFile);
+
+ auto pFontList = getKey( "Font" );
+ if (pFontList == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no Font in " << m_aFile);
+ }
+
+ // fill in direct values
+ if( (pKey = getKey( "print-color-mode" )) )
+ m_bColorDevice = pKey->countValues() > 1;
+}
+
+PPDParser::PPDParser( const OUString& rFile ) :
+ m_aFile( rFile ),
+ m_bColorDevice( false ),
+ m_bType42Capable( false ),
+ m_nLanguageLevel( 0 ),
+ m_aFileEncoding( RTL_TEXTENCODING_MS_1252 ),
+ m_pImageableAreas( nullptr ),
+ m_pDefaultPaperDimension( nullptr ),
+ m_pPaperDimensions( nullptr ),
+ m_pDefaultInputSlot( nullptr ),
+ m_pDefaultResolution( nullptr ),
+ m_pTranslator( new PPDTranslator() )
+{
+ // read in the file
+ std::vector< OString > aLines;
+ PPDDecompressStream aStream( m_aFile );
+ if( aStream.IsOpen() )
+ {
+ bool bLanguageEncoding = false;
+ while( ! aStream.eof() )
+ {
+ OString aCurLine = aStream.ReadLine();
+ if( aCurLine.startsWith("*") )
+ {
+ if (aCurLine.matchIgnoreAsciiCase("*include:"))
+ {
+ aCurLine = aCurLine.copy(9);
+ aCurLine = comphelper::string::strip(aCurLine, ' ');
+ aCurLine = comphelper::string::strip(aCurLine, '\t');
+ aCurLine = comphelper::string::stripEnd(aCurLine, '\r');
+ aCurLine = comphelper::string::stripEnd(aCurLine, '\n');
+ aCurLine = comphelper::string::strip(aCurLine, '"');
+ aStream.Close();
+ aStream.Open(getPPDFile(OStringToOUString(aCurLine, m_aFileEncoding)));
+ continue;
+ }
+ else if( ! bLanguageEncoding &&
+ aCurLine.matchIgnoreAsciiCase("*languageencoding") )
+ {
+ bLanguageEncoding = true; // generally only the first one counts
+ OString aLower = aCurLine.toAsciiLowerCase();
+ if( aLower.indexOf("isolatin1", 17 ) != -1 ||
+ aLower.indexOf("windowsansi", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_MS_1252;
+ else if( aLower.indexOf("isolatin2", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_ISO_8859_2;
+ else if( aLower.indexOf("isolatin5", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_ISO_8859_5;
+ else if( aLower.indexOf("jis83-rksj", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_SHIFT_JIS;
+ else if( aLower.indexOf("macstandard", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_APPLE_ROMAN;
+ else if( aLower.indexOf("utf-8", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_UTF8;
+ }
+ }
+ aLines.push_back( aCurLine );
+ }
+ }
+ aStream.Close();
+
+ // now get the Values
+ parse( aLines );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "acquired " << m_aKeys.size()
+ << " Keys from PPD " << m_aFile << ":");
+ for (auto const& key : m_aKeys)
+ {
+ const PPDKey* pKey = key.second.get();
+ char const* pSetupType = "<unknown>";
+ switch( pKey->m_eSetupType )
+ {
+ case PPDKey::SetupType::ExitServer: pSetupType = "ExitServer";break;
+ case PPDKey::SetupType::Prolog: pSetupType = "Prolog";break;
+ case PPDKey::SetupType::DocumentSetup: pSetupType = "DocumentSetup";break;
+ case PPDKey::SetupType::PageSetup: pSetupType = "PageSetup";break;
+ case PPDKey::SetupType::JCLSetup: pSetupType = "JCLSetup";break;
+ case PPDKey::SetupType::AnySetup: pSetupType = "AnySetup";break;
+ default: break;
+ }
+ SAL_INFO("vcl.unx.print", "\t\"" << pKey->getKey() << "\" ("
+ << pKey->countValues() << "values) OrderDependency: "
+ << pKey->m_nOrderDependency << pSetupType );
+ for( int j = 0; j < pKey->countValues(); j++ )
+ {
+ const PPDValue* pValue = pKey->getValue( j );
+ char const* pVType = "<unknown>";
+ switch( pValue->m_eType )
+ {
+ case eInvocation: pVType = "invocation";break;
+ case eQuoted: pVType = "quoted";break;
+ case eString: pVType = "string";break;
+ case eSymbol: pVType = "symbol";break;
+ case eNo: pVType = "no";break;
+ default: break;
+ }
+ SAL_INFO("vcl.unx.print", "\t\t"
+ << (pValue == pKey->m_pDefaultValue ? "(Default:) " : "")
+ << "option: \"" << pValue->m_aOption
+ << "\", value: type " << pVType << " \""
+ << pValue->m_aValue << "\"");
+ }
+ }
+ SAL_INFO("vcl.unx.print",
+ "constraints: (" << m_aConstraints.size() << " found)");
+ for (auto const& constraint : m_aConstraints)
+ {
+ SAL_INFO("vcl.unx.print", "*\"" << constraint.m_pKey1->getKey() << "\" \""
+ << (constraint.m_pOption1 ? constraint.m_pOption1->m_aOption : "<nil>")
+ << "\" *\"" << constraint.m_pKey2->getKey() << "\" \""
+ << (constraint.m_pOption2 ? constraint.m_pOption2->m_aOption : "<nil>")
+ << "\"");
+ }
+#endif
+
+ // fill in shortcuts
+ const PPDKey* pKey;
+
+ m_pImageableAreas = getKey( "ImageableArea" );
+ const PPDValue * pDefaultImageableArea = nullptr;
+ if( m_pImageableAreas )
+ pDefaultImageableArea = m_pImageableAreas->getDefaultValue();
+ if (m_pImageableAreas == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no ImageableArea in " << m_aFile);
+ }
+ if (pDefaultImageableArea == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no DefaultImageableArea in " << m_aFile);
+ }
+
+ m_pPaperDimensions = getKey( "PaperDimension" );
+ if( m_pPaperDimensions )
+ m_pDefaultPaperDimension = m_pPaperDimensions->getDefaultValue();
+ if (m_pPaperDimensions == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no PaperDimensions in " << m_aFile);
+ }
+ if (m_pDefaultPaperDimension == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no DefaultPaperDimensions in " << m_aFile);
+ }
+
+ auto pResolutions = getKey( "Resolution" );
+ if( pResolutions )
+ m_pDefaultResolution = pResolutions->getDefaultValue();
+ if (pResolutions == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no Resolution in " << m_aFile);
+ }
+ SAL_INFO_IF(!m_pDefaultResolution, "vcl.unx.print", "no DefaultResolution in " + m_aFile);
+
+ auto pInputSlots = getKey( "InputSlot" );
+ if( pInputSlots )
+ m_pDefaultInputSlot = pInputSlots->getDefaultValue();
+ SAL_INFO_IF(!pInputSlots, "vcl.unx.print", "no InputSlot in " << m_aFile);
+ SAL_INFO_IF(!m_pDefaultInputSlot, "vcl.unx.print", "no DefaultInputSlot in " << m_aFile);
+
+ auto pFontList = getKey( "Font" );
+ if (pFontList == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no Font in " << m_aFile);
+ }
+
+ // fill in direct values
+ if ((pKey = getKey("ColorDevice")))
+ {
+ if (const PPDValue* pValue = pKey->getValue(0))
+ m_bColorDevice = pValue->m_aValue.startsWithIgnoreAsciiCase("true");
+ }
+
+ if ((pKey = getKey("LanguageLevel")))
+ {
+ if (const PPDValue* pValue = pKey->getValue(0))
+ m_nLanguageLevel = pValue->m_aValue.toInt32();
+ }
+ if ((pKey = getKey("TTRasterizer")))
+ {
+ if (const PPDValue* pValue = pKey->getValue(0))
+ m_bType42Capable = pValue->m_aValue.equalsIgnoreAsciiCase( "Type42" );
+ }
+}
+
+PPDParser::~PPDParser()
+{
+ m_pTranslator.reset();
+}
+
+void PPDParser::insertKey( std::unique_ptr<PPDKey> pKey )
+{
+ m_aOrderedKeys.push_back( pKey.get() );
+ m_aKeys[ pKey->getKey() ] = std::move(pKey);
+}
+
+const PPDKey* PPDParser::getKey( int n ) const
+{
+ return (n >= 0 && o3tl::make_unsigned(n) < m_aOrderedKeys.size()) ? m_aOrderedKeys[n] : nullptr;
+}
+
+const PPDKey* PPDParser::getKey( const OUString& rKey ) const
+{
+ PPDParser::hash_type::const_iterator it = m_aKeys.find( rKey );
+ return it != m_aKeys.end() ? it->second.get() : nullptr;
+}
+
+bool PPDParser::hasKey( const PPDKey* pKey ) const
+{
+ return pKey && ( m_aKeys.find( pKey->getKey() ) != m_aKeys.end() );
+}
+
+static sal_uInt8 getNibble( char cChar )
+{
+ sal_uInt8 nRet = 0;
+ if( cChar >= '0' && cChar <= '9' )
+ nRet = sal_uInt8( cChar - '0' );
+ else if( cChar >= 'A' && cChar <= 'F' )
+ nRet = 10 + sal_uInt8( cChar - 'A' );
+ else if( cChar >= 'a' && cChar <= 'f' )
+ nRet = 10 + sal_uInt8( cChar - 'a' );
+ return nRet;
+}
+
+OUString PPDParser::handleTranslation(const OString& i_rString, bool bIsGlobalized)
+{
+ sal_Int32 nOrigLen = i_rString.getLength();
+ OStringBuffer aTrans( nOrigLen );
+ const char* pStr = i_rString.getStr();
+ const char* pEnd = pStr + nOrigLen;
+ while( pStr < pEnd )
+ {
+ if( *pStr == '<' )
+ {
+ pStr++;
+ char cChar;
+ while( *pStr != '>' && pStr < pEnd-1 )
+ {
+ cChar = getNibble( *pStr++ ) << 4;
+ cChar |= getNibble( *pStr++ );
+ aTrans.append( cChar );
+ }
+ pStr++;
+ }
+ else
+ aTrans.append( *pStr++ );
+ }
+ return OStringToOUString( aTrans, bIsGlobalized ? RTL_TEXTENCODING_UTF8 : m_aFileEncoding );
+}
+
+namespace
+{
+ bool oddDoubleQuoteCount(OStringBuffer &rBuffer)
+ {
+ bool bHasOddCount = false;
+ for (sal_Int32 i = 0; i < rBuffer.getLength(); ++i)
+ {
+ if (rBuffer[i] == '"')
+ bHasOddCount = !bHasOddCount;
+ }
+ return bHasOddCount;
+ }
+}
+
+void PPDParser::parse( ::std::vector< OString >& rLines )
+{
+ // Name for PPD group into which all options are put for which the PPD
+ // does not explicitly define a group.
+ // This is similar to how CUPS handles it,
+ // s. Sweet, Michael R. (2001): Common UNIX Printing System, p. 251:
+ // "Each option in turn is associated with a group stored in the
+ // ppd_group_t structure. Groups can be specified in the PPD file; if an
+ // option is not associated with a group, it is put in a "General" or
+ // "Extra" group depending on the option.
+ static constexpr OStringLiteral aDefaultPPDGroupName("General");
+
+ std::vector< OString >::iterator line = rLines.begin();
+ PPDParser::hash_type::const_iterator keyit;
+
+ // name of the PPD group that is currently being processed
+ OString aCurrentGroup = aDefaultPPDGroupName;
+
+ while( line != rLines.end() )
+ {
+ OString aCurrentLine( *line );
+ ++line;
+
+ SAL_INFO("vcl.unx.print", "Parse line '" << aCurrentLine << "'");
+
+ if (aCurrentLine.getLength() < 2 || aCurrentLine[0] != '*')
+ continue;
+ if( aCurrentLine[1] == '%' )
+ continue;
+
+ OString aKey = GetCommandLineToken( 0, aCurrentLine.getToken(0, ':') );
+ sal_Int32 nPos = aKey.indexOf('/');
+ if (nPos != -1)
+ aKey = aKey.copy(0, nPos);
+ if(!aKey.isEmpty())
+ {
+ aKey = aKey.copy(1); // remove the '*'
+ }
+ if(aKey.isEmpty())
+ {
+ continue;
+ }
+
+ if (aKey == "CloseGroup")
+ {
+ aCurrentGroup = aDefaultPPDGroupName;
+ continue;
+ }
+ if (aKey == "OpenGroup")
+ {
+ OString aGroupName = aCurrentLine;
+ sal_Int32 nPosition = aGroupName.indexOf('/');
+ if (nPosition != -1)
+ {
+ aGroupName = aGroupName.copy(0, nPosition);
+ }
+
+ aCurrentGroup = GetCommandLineToken(1, aGroupName);
+ continue;
+ }
+ if ((aKey == "CloseUI") ||
+ (aKey == "JCLCloseUI") ||
+ (aKey == "End") ||
+ (aKey == "JCLEnd") ||
+ (aKey == "OpenSubGroup") ||
+ (aKey == "CloseSubGroup"))
+ {
+ continue;
+ }
+
+ if ((aKey == "OpenUI") || (aKey == "JCLOpenUI"))
+ {
+ parseOpenUI( aCurrentLine, aCurrentGroup);
+ continue;
+ }
+ else if (aKey == "OrderDependency")
+ {
+ parseOrderDependency( aCurrentLine );
+ continue;
+ }
+ else if (aKey == "UIConstraints" ||
+ aKey == "NonUIConstraints")
+ {
+ continue; // parsed in pass 2
+ }
+ else if( aKey == "CustomPageSize" ) // currently not handled
+ continue;
+ else if (aKey.startsWith("Custom", &aKey) )
+ {
+ //fdo#43049 very basic support for Custom entries, we ignore the
+ //validation params and types
+ OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
+ keyit = m_aKeys.find( aUniKey );
+ if(keyit != m_aKeys.end())
+ {
+ PPDKey* pKey = keyit->second.get();
+ pKey->insertValue("Custom", eInvocation, true);
+ }
+ continue;
+ }
+
+ // default values are parsed in pass 2
+ if (aKey.startsWith("Default"))
+ continue;
+
+ bool bQuery = false;
+ if (aKey[0] == '?')
+ {
+ aKey = aKey.copy(1);
+ bQuery = true;
+ }
+
+ OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
+ // handle CUPS extension for globalized PPDs
+ /* FIXME-BCP47: really only ISO 639-1 two character language codes?
+ * goodnight... */
+ bool bIsGlobalizedLine = false;
+ css::lang::Locale aTransLocale;
+ if( ( aUniKey.getLength() > 3 && aUniKey[ 2 ] == '.' ) ||
+ ( aUniKey.getLength() > 5 && aUniKey[ 2 ] == '_' && aUniKey[ 5 ] == '.' ) )
+ {
+ if( aUniKey[ 2 ] == '.' )
+ {
+ aTransLocale.Language = aUniKey.copy( 0, 2 );
+ aUniKey = aUniKey.copy( 3 );
+ }
+ else
+ {
+ aTransLocale.Language = aUniKey.copy( 0, 2 );
+ aTransLocale.Country = aUniKey.copy( 3, 2 );
+ aUniKey = aUniKey.copy( 6 );
+ }
+ bIsGlobalizedLine = true;
+ }
+
+ OUString aOption;
+ nPos = aCurrentLine.indexOf(':');
+ if( nPos != -1 )
+ {
+ aOption = OStringToOUString(
+ aCurrentLine.subView( 1, nPos-1 ), RTL_TEXTENCODING_MS_1252 );
+ aOption = GetCommandLineToken( 1, aOption );
+ sal_Int32 nTransPos = aOption.indexOf( '/' );
+ if( nTransPos != -1 )
+ aOption = aOption.copy(0, nTransPos);
+ }
+
+ PPDValueType eType = eNo;
+ OUString aValue;
+ OUString aOptionTranslation;
+ OUString aValueTranslation;
+ if( nPos != -1 )
+ {
+ // found a colon, there may be an option
+ OString aLine = aCurrentLine.copy( 1, nPos-1 );
+ aLine = WhitespaceToSpace( aLine );
+ sal_Int32 nTransPos = aLine.indexOf('/');
+ if (nTransPos != -1)
+ aOptionTranslation = handleTranslation( aLine.copy(nTransPos+1), bIsGlobalizedLine );
+
+ // read in more lines if necessary for multiline values
+ aLine = aCurrentLine.copy( nPos+1 );
+ if (!aLine.isEmpty())
+ {
+ OStringBuffer aBuffer(aLine);
+ while (line != rLines.end() && oddDoubleQuoteCount(aBuffer))
+ {
+ // copy the newlines also
+ aBuffer.append('\n');
+ aBuffer.append(*line);
+ ++line;
+ }
+ aLine = aBuffer.makeStringAndClear();
+ }
+ aLine = WhitespaceToSpace( aLine );
+
+ // #i100644# handle a missing value (actually a broken PPD)
+ if( aLine.isEmpty() )
+ {
+ if( !aOption.isEmpty() &&
+ !aUniKey.startsWith( "JCL" ) )
+ eType = eInvocation;
+ else
+ eType = eQuoted;
+ }
+ // check for invocation or quoted value
+ else if(aLine[0] == '"')
+ {
+ aLine = aLine.copy(1);
+ nTransPos = aLine.indexOf('"');
+ if (nTransPos == -1)
+ nTransPos = aLine.getLength();
+ aValue = OStringToOUString(aLine.subView(0, nTransPos), RTL_TEXTENCODING_MS_1252);
+ // after the second doublequote can follow a / and a translation
+ if (nTransPos < aLine.getLength() - 2)
+ {
+ aValueTranslation = handleTranslation( aLine.copy( nTransPos+2 ), bIsGlobalizedLine );
+ }
+ // check for quoted value
+ if( !aOption.isEmpty() &&
+ !aUniKey.startsWith( "JCL" ) )
+ eType = eInvocation;
+ else
+ eType = eQuoted;
+ }
+ // check for symbol value
+ else if(aLine[0] == '^')
+ {
+ aLine = aLine.copy(1);
+ aValue = OStringToOUString(aLine, RTL_TEXTENCODING_MS_1252);
+ eType = eSymbol;
+ }
+ else
+ {
+ // must be a string value then
+ // strictly this is false because string values
+ // can contain any whitespace which is reduced
+ // to one space by now
+ // who cares ...
+ nTransPos = aLine.indexOf('/');
+ if (nTransPos == -1)
+ nTransPos = aLine.getLength();
+ aValue = OStringToOUString(aLine.subView(0, nTransPos), RTL_TEXTENCODING_MS_1252);
+ if (nTransPos+1 < aLine.getLength())
+ aValueTranslation = handleTranslation( aLine.copy( nTransPos+1 ), bIsGlobalizedLine );
+ eType = eString;
+ }
+ }
+
+ // handle globalized PPD entries
+ if( bIsGlobalizedLine )
+ {
+ // handle main key translations of form:
+ // *ll_CC.Translation MainKeyword/translated text: ""
+ if( aUniKey == "Translation" )
+ {
+ m_pTranslator->insertKey( aOption, aOptionTranslation, aTransLocale );
+ }
+ // handle options translations of for:
+ // *ll_CC.MainKeyword OptionKeyword/translated text: ""
+ else
+ {
+ m_pTranslator->insertOption( aUniKey, aOption, aOptionTranslation, aTransLocale );
+ }
+ continue;
+ }
+
+ PPDKey* pKey = nullptr;
+ keyit = m_aKeys.find( aUniKey );
+ if( keyit == m_aKeys.end() )
+ {
+ pKey = new PPDKey( aUniKey );
+ insertKey( std::unique_ptr<PPDKey>(pKey) );
+ }
+ else
+ pKey = keyit->second.get();
+
+ if( eType == eNo && bQuery )
+ continue;
+
+ PPDValue* pValue = pKey->insertValue( aOption, eType );
+ if( ! pValue )
+ continue;
+ pValue->m_aValue = aValue;
+
+ if( !aOptionTranslation.isEmpty() )
+ m_pTranslator->insertOption( aUniKey, aOption, aOptionTranslation, aTransLocale );
+ if( !aValueTranslation.isEmpty() )
+ m_pTranslator->insertValue( aUniKey, aOption, aValue, aValueTranslation, aTransLocale );
+
+ // eventually update query and remove from option list
+ if( bQuery && !pKey->m_bQueryValue )
+ {
+ pKey->m_bQueryValue = true;
+ pKey->eraseValue( pValue->m_aOption );
+ }
+ }
+
+ // second pass: fill in defaults
+ for( const auto& aLine : rLines )
+ {
+ if (aLine.startsWith("*Default"))
+ {
+ SAL_INFO("vcl.unx.print", "Found a default: '" << aLine << "'");
+ OUString aKey(OStringToOUString(aLine.subView(8), RTL_TEXTENCODING_MS_1252));
+ sal_Int32 nPos = aKey.indexOf( ':' );
+ if( nPos != -1 )
+ {
+ aKey = aKey.copy(0, nPos);
+ OUString aOption(OStringToOUString(
+ WhitespaceToSpace(aLine.subView(nPos+9)),
+ RTL_TEXTENCODING_MS_1252));
+ keyit = m_aKeys.find( aKey );
+ if( keyit != m_aKeys.end() )
+ {
+ PPDKey* pKey = keyit->second.get();
+ const PPDValue* pDefValue = pKey->getValue( aOption );
+ if( pKey->m_pDefaultValue == nullptr )
+ pKey->m_pDefaultValue = pDefValue;
+ }
+ else
+ {
+ // some PPDs contain defaults for keys that
+ // do not exist otherwise
+ // (example: DefaultResolution)
+ // so invent that key here and have a default value
+ std::unique_ptr<PPDKey> pKey(new PPDKey( aKey ));
+ pKey->insertValue( aOption, eInvocation /*or what ?*/ );
+ pKey->m_pDefaultValue = pKey->getValue( aOption );
+ insertKey( std::move(pKey) );
+ }
+ }
+ }
+ else if (aLine.startsWith("*UIConstraints") ||
+ aLine.startsWith("*NonUIConstraints"))
+ {
+ parseConstraint( aLine );
+ }
+ }
+}
+
+void PPDParser::parseOpenUI(const OString& rLine, std::string_view rPPDGroup)
+{
+ OUString aTranslation;
+ OString aKey = rLine;
+
+ sal_Int32 nPos = aKey.indexOf(':');
+ if( nPos != -1 )
+ aKey = aKey.copy(0, nPos);
+ nPos = aKey.indexOf('/');
+ if( nPos != -1 )
+ {
+ aTranslation = handleTranslation( aKey.copy( nPos + 1 ), false );
+ aKey = aKey.copy(0, nPos);
+ }
+ aKey = GetCommandLineToken( 1, aKey );
+ aKey = aKey.copy(1);
+
+ OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
+ PPDParser::hash_type::const_iterator keyit = m_aKeys.find( aUniKey );
+ PPDKey* pKey;
+ if( keyit == m_aKeys.end() )
+ {
+ pKey = new PPDKey( aUniKey );
+ insertKey( std::unique_ptr<PPDKey>(pKey) );
+ }
+ else
+ pKey = keyit->second.get();
+
+ pKey->m_bUIOption = true;
+ m_pTranslator->insertKey( pKey->getKey(), aTranslation );
+
+ pKey->m_aGroup = OStringToOUString(rPPDGroup, RTL_TEXTENCODING_MS_1252);
+}
+
+void PPDParser::parseOrderDependency(const OString& rLine)
+{
+ OString aLine(rLine);
+ sal_Int32 nPos = aLine.indexOf(':');
+ if( nPos != -1 )
+ aLine = aLine.copy( nPos+1 );
+
+ sal_Int32 nOrder = GetCommandLineToken( 0, aLine ).toInt32();
+ OString aSetup = GetCommandLineToken( 1, aLine );
+ OUString aKey(OStringToOUString(GetCommandLineToken(2, aLine), RTL_TEXTENCODING_MS_1252));
+ if( aKey[ 0 ] != '*' )
+ return; // invalid order dependency
+ aKey = aKey.replaceAt( 0, 1, u"" );
+
+ PPDKey* pKey;
+ PPDParser::hash_type::const_iterator keyit = m_aKeys.find( aKey );
+ if( keyit == m_aKeys.end() )
+ {
+ pKey = new PPDKey( aKey );
+ insertKey( std::unique_ptr<PPDKey>(pKey) );
+ }
+ else
+ pKey = keyit->second.get();
+
+ pKey->m_nOrderDependency = nOrder;
+ if( aSetup == "ExitServer" )
+ pKey->m_eSetupType = PPDKey::SetupType::ExitServer;
+ else if( aSetup == "Prolog" )
+ pKey->m_eSetupType = PPDKey::SetupType::Prolog;
+ else if( aSetup == "DocumentSetup" )
+ pKey->m_eSetupType = PPDKey::SetupType::DocumentSetup;
+ else if( aSetup == "PageSetup" )
+ pKey->m_eSetupType = PPDKey::SetupType::PageSetup;
+ else if( aSetup == "JCLSetup" )
+ pKey->m_eSetupType = PPDKey::SetupType::JCLSetup;
+ else
+ pKey->m_eSetupType = PPDKey::SetupType::AnySetup;
+}
+
+void PPDParser::parseConstraint( const OString& rLine )
+{
+ bool bFailed = false;
+
+ OUString aLine(OStringToOUString(rLine, RTL_TEXTENCODING_MS_1252));
+ sal_Int32 nIdx = rLine.indexOf(':');
+ if (nIdx != -1)
+ aLine = aLine.replaceAt(0, nIdx + 1, u"");
+ PPDConstraint aConstraint;
+ int nTokens = GetCommandLineTokenCount( aLine );
+ for( int i = 0; i < nTokens; i++ )
+ {
+ OUString aToken = GetCommandLineToken( i, aLine );
+ if( !aToken.isEmpty() && aToken[ 0 ] == '*' )
+ {
+ aToken = aToken.replaceAt( 0, 1, u"" );
+ if( aConstraint.m_pKey1 )
+ aConstraint.m_pKey2 = getKey( aToken );
+ else
+ aConstraint.m_pKey1 = getKey( aToken );
+ }
+ else
+ {
+ if( aConstraint.m_pKey2 )
+ {
+ if( ! ( aConstraint.m_pOption2 = aConstraint.m_pKey2->getValue( aToken ) ) )
+ bFailed = true;
+ }
+ else if( aConstraint.m_pKey1 )
+ {
+ if( ! ( aConstraint.m_pOption1 = aConstraint.m_pKey1->getValue( aToken ) ) )
+ bFailed = true;
+ }
+ else
+ // constraint for nonexistent keys; this happens
+ // e.g. in HP4PLUS3
+ bFailed = true;
+ }
+ }
+ // there must be two keywords
+ if( ! aConstraint.m_pKey1 || ! aConstraint.m_pKey2 || bFailed )
+ {
+ SAL_INFO("vcl.unx.print",
+ "Warning: constraint \"" << rLine << "\" is invalid");
+ }
+ else
+ m_aConstraints.push_back( aConstraint );
+}
+
+OUString PPDParser::getDefaultPaperDimension() const
+{
+ if( m_pDefaultPaperDimension )
+ return m_pDefaultPaperDimension->m_aOption;
+
+ return OUString();
+}
+
+bool PPDParser::getMargins(
+ std::u16string_view rPaperName,
+ int& rLeft, int& rRight,
+ int& rUpper, int& rLower ) const
+{
+ if( ! m_pImageableAreas || ! m_pPaperDimensions )
+ return false;
+
+ int nPDim=-1, nImArea=-1, i;
+ for( i = 0; i < m_pImageableAreas->countValues(); i++ )
+ if( rPaperName == m_pImageableAreas->getValue( i )->m_aOption )
+ nImArea = i;
+ for( i = 0; i < m_pPaperDimensions->countValues(); i++ )
+ if( rPaperName == m_pPaperDimensions->getValue( i )->m_aOption )
+ nPDim = i;
+ if( nPDim == -1 || nImArea == -1 )
+ return false;
+
+ double ImLLx, ImLLy, ImURx, ImURy;
+ double PDWidth, PDHeight;
+ OUString aArea = m_pImageableAreas->getValue( nImArea )->m_aValue;
+ ImLLx = StringToDouble( GetCommandLineToken( 0, aArea ) );
+ ImLLy = StringToDouble( GetCommandLineToken( 1, aArea ) );
+ ImURx = StringToDouble( GetCommandLineToken( 2, aArea ) );
+ ImURy = StringToDouble( GetCommandLineToken( 3, aArea ) );
+ aArea = m_pPaperDimensions->getValue( nPDim )->m_aValue;
+ PDWidth = StringToDouble( GetCommandLineToken( 0, aArea ) );
+ PDHeight = StringToDouble( GetCommandLineToken( 1, aArea ) );
+ rLeft = static_cast<int>(ImLLx + 0.5);
+ rLower = static_cast<int>(ImLLy + 0.5);
+ rUpper = static_cast<int>(PDHeight - ImURy + 0.5);
+ rRight = static_cast<int>(PDWidth - ImURx + 0.5);
+
+ return true;
+}
+
+bool PPDParser::getPaperDimension(
+ std::u16string_view rPaperName,
+ int& rWidth, int& rHeight ) const
+{
+ if( ! m_pPaperDimensions )
+ return false;
+
+ int nPDim=-1;
+ for( int i = 0; i < m_pPaperDimensions->countValues(); i++ )
+ if( rPaperName == m_pPaperDimensions->getValue( i )->m_aOption )
+ nPDim = i;
+ if( nPDim == -1 )
+ return false;
+
+ double PDWidth, PDHeight;
+ OUString aArea = m_pPaperDimensions->getValue( nPDim )->m_aValue;
+ PDWidth = StringToDouble( GetCommandLineToken( 0, aArea ) );
+ PDHeight = StringToDouble( GetCommandLineToken( 1, aArea ) );
+ rHeight = static_cast<int>(PDHeight + 0.5);
+ rWidth = static_cast<int>(PDWidth + 0.5);
+
+ return true;
+}
+
+OUString PPDParser::matchPaper( int nWidth, int nHeight ) const
+{
+ if( ! m_pPaperDimensions )
+ return OUString();
+
+ int nPDim = -1;
+ double fSort = 2e36, fNewSort;
+
+ for( int i = 0; i < m_pPaperDimensions->countValues(); i++ )
+ {
+ OUString aArea = m_pPaperDimensions->getValue( i )->m_aValue;
+ double PDWidth = StringToDouble( GetCommandLineToken( 0, aArea ) );
+ double PDHeight = StringToDouble( GetCommandLineToken( 1, aArea ) );
+ PDWidth /= static_cast<double>(nWidth);
+ PDHeight /= static_cast<double>(nHeight);
+ if( PDWidth >= 0.9 && PDWidth <= 1.1 &&
+ PDHeight >= 0.9 && PDHeight <= 1.1 )
+ {
+ fNewSort =
+ (1.0-PDWidth)*(1.0-PDWidth) + (1.0-PDHeight)*(1.0-PDHeight);
+ if( fNewSort == 0.0 ) // perfect match
+ return m_pPaperDimensions->getValue( i )->m_aOption;
+
+ if( fNewSort < fSort )
+ {
+ fSort = fNewSort;
+ nPDim = i;
+ }
+ }
+ }
+
+ static bool bDontSwap = false;
+ if( nPDim == -1 && ! bDontSwap )
+ {
+ // swap portrait/landscape and try again
+ bDontSwap = true;
+ OUString rRet = matchPaper( nHeight, nWidth );
+ bDontSwap = false;
+ return rRet;
+ }
+
+ return nPDim != -1 ? m_pPaperDimensions->getValue( nPDim )->m_aOption : OUString();
+}
+
+OUString PPDParser::getDefaultInputSlot() const
+{
+ if( m_pDefaultInputSlot )
+ return m_pDefaultInputSlot->m_aValue;
+ return OUString();
+}
+
+void PPDParser::getResolutionFromString(std::u16string_view rString,
+ int& rXRes, int& rYRes )
+{
+ rXRes = rYRes = 300;
+
+ const size_t nDPIPos {rString.find( u"dpi" )};
+ if( nDPIPos != std::u16string_view::npos )
+ {
+ const size_t nPos {rString.find( 'x' )};
+ if( nPos != std::u16string_view::npos )
+ {
+ rXRes = o3tl::toInt32(rString.substr( 0, nPos ));
+ rYRes = o3tl::toInt32(rString.substr(nPos+1, nDPIPos - nPos - 1));
+ }
+ else
+ rXRes = rYRes = o3tl::toInt32(rString.substr( 0, nDPIPos ));
+ }
+}
+
+void PPDParser::getDefaultResolution( int& rXRes, int& rYRes ) const
+{
+ if( m_pDefaultResolution )
+ {
+ getResolutionFromString( m_pDefaultResolution->m_aValue, rXRes, rYRes );
+ return;
+ }
+
+ rXRes = 300;
+ rYRes = 300;
+}
+
+OUString PPDParser::translateKey( const OUString& i_rKey ) const
+{
+ OUString aResult( m_pTranslator->translateKey( i_rKey ) );
+ if( aResult.isEmpty() )
+ aResult = i_rKey;
+ return aResult;
+}
+
+OUString PPDParser::translateOption( const OUString& i_rKey,
+ const OUString& i_rOption ) const
+{
+ OUString aResult( m_pTranslator->translateOption( i_rKey, i_rOption ) );
+ if( aResult.isEmpty() )
+ aResult = i_rOption;
+ return aResult;
+}
+
+/*
+ * PPDKey
+ */
+
+PPDKey::PPDKey( const OUString& rKey ) :
+ m_aKey( rKey ),
+ m_pDefaultValue( nullptr ),
+ m_bQueryValue( false ),
+ m_bUIOption( false ),
+ m_nOrderDependency( 100 ),
+ m_eSetupType( SetupType::AnySetup )
+{
+}
+
+PPDKey::~PPDKey()
+{
+}
+
+const PPDValue* PPDKey::getValue( int n ) const
+{
+ return (n >= 0 && o3tl::make_unsigned(n) < m_aOrderedValues.size()) ? m_aOrderedValues[n] : nullptr;
+}
+
+const PPDValue* PPDKey::getValue( const OUString& rOption ) const
+{
+ PPDKey::hash_type::const_iterator it = m_aValues.find( rOption );
+ return it != m_aValues.end() ? &it->second : nullptr;
+}
+
+const PPDValue* PPDKey::getValueCaseInsensitive( const OUString& rOption ) const
+{
+ const PPDValue* pValue = getValue( rOption );
+ if( ! pValue )
+ {
+ for( size_t n = 0; n < m_aOrderedValues.size() && ! pValue; n++ )
+ if( m_aOrderedValues[n]->m_aOption.equalsIgnoreAsciiCase( rOption ) )
+ pValue = m_aOrderedValues[n];
+ }
+
+ return pValue;
+}
+
+void PPDKey::eraseValue( const OUString& rOption )
+{
+ PPDKey::hash_type::iterator it = m_aValues.find( rOption );
+ if( it == m_aValues.end() )
+ return;
+
+ auto vit = std::find(m_aOrderedValues.begin(), m_aOrderedValues.end(), &(it->second ));
+ if( vit != m_aOrderedValues.end() )
+ m_aOrderedValues.erase( vit );
+
+ m_aValues.erase( it );
+}
+
+PPDValue* PPDKey::insertValue(const OUString& rOption, PPDValueType eType, bool bCustomOption)
+{
+ if( m_aValues.find( rOption ) != m_aValues.end() )
+ return nullptr;
+
+ PPDValue aValue;
+ aValue.m_aOption = rOption;
+ aValue.m_bCustomOption = bCustomOption;
+ aValue.m_eType = eType;
+ m_aValues[ rOption ] = aValue;
+ PPDValue* pValue = &m_aValues[rOption];
+ m_aOrderedValues.push_back( pValue );
+ return pValue;
+}
+
+/*
+ * PPDContext
+ */
+
+PPDContext::PPDContext() :
+ m_pParser( nullptr )
+{
+}
+
+PPDContext& PPDContext::operator=( PPDContext&& rCopy )
+{
+ std::swap(m_pParser, rCopy.m_pParser);
+ std::swap(m_aCurrentValues, rCopy.m_aCurrentValues);
+ return *this;
+}
+
+const PPDKey* PPDContext::getModifiedKey( std::size_t n ) const
+{
+ if( m_aCurrentValues.size() <= n )
+ return nullptr;
+
+ hash_type::const_iterator it = m_aCurrentValues.begin();
+ std::advance(it, n);
+ return it->first;
+}
+
+void PPDContext::setParser( const PPDParser* pParser )
+{
+ if( pParser != m_pParser )
+ {
+ m_aCurrentValues.clear();
+ m_pParser = pParser;
+ }
+}
+
+const PPDValue* PPDContext::getValue( const PPDKey* pKey ) const
+{
+ if( ! m_pParser )
+ return nullptr;
+
+ hash_type::const_iterator it = m_aCurrentValues.find( pKey );
+ if( it != m_aCurrentValues.end() )
+ return it->second;
+
+ if( ! m_pParser->hasKey( pKey ) )
+ return nullptr;
+
+ const PPDValue* pValue = pKey->getDefaultValue();
+ if( ! pValue )
+ pValue = pKey->getValue( 0 );
+
+ return pValue;
+}
+
+const PPDValue* PPDContext::setValue( const PPDKey* pKey, const PPDValue* pValue, bool bDontCareForConstraints )
+{
+ if( ! m_pParser || ! pKey )
+ return nullptr;
+
+ // pValue can be NULL - it means ignore this option
+
+ if( ! m_pParser->hasKey( pKey ) )
+ return nullptr;
+
+ // check constraints
+ if( pValue )
+ {
+ if( bDontCareForConstraints )
+ {
+ m_aCurrentValues[ pKey ] = pValue;
+ }
+ else if( checkConstraints( pKey, pValue, true ) )
+ {
+ m_aCurrentValues[ pKey ] = pValue;
+
+ // after setting this value, check all constraints !
+ hash_type::iterator it = m_aCurrentValues.begin();
+ while( it != m_aCurrentValues.end() )
+ {
+ if( it->first != pKey &&
+ ! checkConstraints( it->first, it->second, false ) )
+ {
+ SAL_INFO("vcl.unx.print", "PPDContext::setValue: option "
+ << it->first->getKey()
+ << " (" << it->second->m_aOption
+ << ") is constrained after setting "
+ << pKey->getKey()
+ << " to " << pValue->m_aOption);
+ resetValue( it->first, true );
+ it = m_aCurrentValues.begin();
+ }
+ else
+ ++it;
+ }
+ }
+ }
+ else
+ m_aCurrentValues[ pKey ] = nullptr;
+
+ return pValue;
+}
+
+bool PPDContext::checkConstraints( const PPDKey* pKey, const PPDValue* pValue )
+{
+ if( ! m_pParser || ! pKey || ! pValue )
+ return false;
+
+ // ensure that this key is already in the list if it exists at all
+ if( m_aCurrentValues.find( pKey ) != m_aCurrentValues.end() )
+ return checkConstraints( pKey, pValue, false );
+
+ // it is not in the list, insert it temporarily
+ bool bRet = false;
+ if( m_pParser->hasKey( pKey ) )
+ {
+ const PPDValue* pDefValue = pKey->getDefaultValue();
+ m_aCurrentValues[ pKey ] = pDefValue;
+ bRet = checkConstraints( pKey, pValue, false );
+ m_aCurrentValues.erase( pKey );
+ }
+
+ return bRet;
+}
+
+bool PPDContext::resetValue( const PPDKey* pKey, bool bDefaultable )
+{
+ if( ! pKey || ! m_pParser || ! m_pParser->hasKey( pKey ) )
+ return false;
+
+ const PPDValue* pResetValue = pKey->getValue( "None" );
+ if( ! pResetValue )
+ pResetValue = pKey->getValue( "False" );
+ if( ! pResetValue && bDefaultable )
+ pResetValue = pKey->getDefaultValue();
+
+ bool bRet = pResetValue && ( setValue( pKey, pResetValue ) == pResetValue );
+
+ return bRet;
+}
+
+bool PPDContext::checkConstraints( const PPDKey* pKey, const PPDValue* pNewValue, bool bDoReset )
+{
+ if( ! pNewValue )
+ return true;
+
+ // sanity checks
+ if( ! m_pParser )
+ return false;
+
+ if( pKey->getValue( pNewValue->m_aOption ) != pNewValue )
+ return false;
+
+ // None / False and the default can always be set, but be careful !
+ // setting them might influence constrained values
+ if( pNewValue->m_aOption == "None" || pNewValue->m_aOption == "False" ||
+ pNewValue == pKey->getDefaultValue() )
+ return true;
+
+ const ::std::vector< PPDParser::PPDConstraint >& rConstraints( m_pParser->getConstraints() );
+ for (auto const& constraint : rConstraints)
+ {
+ const PPDKey* pLeft = constraint.m_pKey1;
+ const PPDKey* pRight = constraint.m_pKey2;
+ if( ! pLeft || ! pRight || ( pKey != pLeft && pKey != pRight ) )
+ continue;
+
+ const PPDKey* pOtherKey = pKey == pLeft ? pRight : pLeft;
+ const PPDValue* pOtherKeyOption = pKey == pLeft ? constraint.m_pOption2 : constraint.m_pOption1;
+ const PPDValue* pKeyOption = pKey == pLeft ? constraint.m_pOption1 : constraint.m_pOption2;
+
+ // syntax *Key1 option1 *Key2 option2
+ if( pKeyOption && pOtherKeyOption )
+ {
+ if( pNewValue != pKeyOption )
+ continue;
+ if( pOtherKeyOption == getValue( pOtherKey ) )
+ {
+ return false;
+ }
+ }
+ // syntax *Key1 option *Key2 or *Key1 *Key2 option
+ else if( pOtherKeyOption || pKeyOption )
+ {
+ if( pKeyOption )
+ {
+ if( ! ( pOtherKeyOption = getValue( pOtherKey ) ) )
+ continue; // this should not happen, PPD broken
+
+ if( pKeyOption == pNewValue &&
+ pOtherKeyOption->m_aOption != "None" &&
+ pOtherKeyOption->m_aOption != "False" )
+ {
+ // check if the other value can be reset and
+ // do so if possible
+ if( bDoReset && resetValue( pOtherKey ) )
+ continue;
+
+ return false;
+ }
+ }
+ else if( pOtherKeyOption )
+ {
+ if( getValue( pOtherKey ) == pOtherKeyOption &&
+ pNewValue->m_aOption != "None" &&
+ pNewValue->m_aOption != "False" )
+ return false;
+ }
+ else
+ {
+ // this should not happen, PPD is broken
+ }
+ }
+ // syntax *Key1 *Key2
+ else
+ {
+ const PPDValue* pOtherValue = getValue( pOtherKey );
+ if( pOtherValue->m_aOption != "None" &&
+ pOtherValue->m_aOption != "False" &&
+ pNewValue->m_aOption != "None" &&
+ pNewValue->m_aOption != "False" )
+ return false;
+ }
+ }
+ return true;
+}
+
+char* PPDContext::getStreamableBuffer( sal_uLong& rBytes ) const
+{
+ rBytes = 0;
+ if( m_aCurrentValues.empty() )
+ return nullptr;
+ for (auto const& elem : m_aCurrentValues)
+ {
+ OString aCopy(OUStringToOString(elem.first->getKey(), RTL_TEXTENCODING_MS_1252));
+ rBytes += aCopy.getLength();
+ rBytes += 1; // for ':'
+ if( elem.second )
+ {
+ aCopy = OUStringToOString(elem.second->m_aOption, RTL_TEXTENCODING_MS_1252);
+ rBytes += aCopy.getLength();
+ }
+ else
+ rBytes += 4;
+ rBytes += 1; // for '\0'
+ }
+ rBytes += 1;
+ char* pBuffer = new char[ rBytes ];
+ memset( pBuffer, 0, rBytes );
+ char* pRun = pBuffer;
+ for (auto const& elem : m_aCurrentValues)
+ {
+ OString aCopy(OUStringToOString(elem.first->getKey(), RTL_TEXTENCODING_MS_1252));
+ int nBytes = aCopy.getLength();
+ memcpy( pRun, aCopy.getStr(), nBytes );
+ pRun += nBytes;
+ *pRun++ = ':';
+ if( elem.second )
+ aCopy = OUStringToOString(elem.second->m_aOption, RTL_TEXTENCODING_MS_1252);
+ else
+ aCopy = "*nil";
+ nBytes = aCopy.getLength();
+ memcpy( pRun, aCopy.getStr(), nBytes );
+ pRun += nBytes;
+
+ *pRun++ = 0;
+ }
+ return pBuffer;
+}
+
+void PPDContext::rebuildFromStreamBuffer(const std::vector<char> &rBuffer)
+{
+ if( ! m_pParser )
+ return;
+
+ m_aCurrentValues.clear();
+
+ const size_t nBytes = rBuffer.size() - 1;
+ size_t nRun = 0;
+ while (nRun < nBytes && rBuffer[nRun])
+ {
+ OString aLine(rBuffer.data() + nRun);
+ sal_Int32 nPos = aLine.indexOf(':');
+ if( nPos != -1 )
+ {
+ const PPDKey* pKey = m_pParser->getKey( OStringToOUString( aLine.subView( 0, nPos ), RTL_TEXTENCODING_MS_1252 ) );
+ if( pKey )
+ {
+ const PPDValue* pValue = nullptr;
+ OUString aOption(
+ OStringToOUString(aLine.subView(nPos+1), RTL_TEXTENCODING_MS_1252));
+ if (aOption != "*nil")
+ pValue = pKey->getValue( aOption );
+ m_aCurrentValues[ pKey ] = pValue;
+ SAL_INFO("vcl.unx.print",
+ "PPDContext::rebuildFromStreamBuffer: read PPDKeyValue { "
+ << pKey->getKey() << " , "
+ << (pValue ? aOption : "<nil>")
+ << " }");
+ }
+ }
+ nRun += aLine.getLength()+1;
+ }
+}
+
+int PPDContext::getRenderResolution() const
+{
+ // initialize to reasonable default, if parser is not set
+ int nDPI = 300;
+ if( m_pParser )
+ {
+ int nDPIx = 300, nDPIy = 300;
+ const PPDKey* pKey = m_pParser->getKey( "Resolution" );
+ if( pKey )
+ {
+ const PPDValue* pValue = getValue( pKey );
+ if( pValue )
+ PPDParser::getResolutionFromString( pValue->m_aOption, nDPIx, nDPIy );
+ else
+ m_pParser->getDefaultResolution( nDPIx, nDPIy );
+ }
+ else
+ m_pParser->getDefaultResolution( nDPIx, nDPIy );
+
+ nDPI = std::max(nDPIx, nDPIy);
+ }
+ return nDPI;
+}
+
+void PPDContext::getPageSize( OUString& rPaper, int& rWidth, int& rHeight ) const
+{
+ // initialize to reasonable default, if parser is not set
+ rPaper = "A4";
+ rWidth = 595;
+ rHeight = 842;
+ if( !m_pParser )
+ return;
+
+ const PPDKey* pKey = m_pParser->getKey( "PageSize" );
+ if( !pKey )
+ return;
+
+ const PPDValue* pValue = getValue( pKey );
+ if( pValue )
+ {
+ rPaper = pValue->m_aOption;
+ m_pParser->getPaperDimension( rPaper, rWidth, rHeight );
+ }
+ else
+ {
+ rPaper = m_pParser->getDefaultPaperDimension();
+ m_pParser->getDefaultPaperDimension( rWidth, rHeight );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/printer/printerinfomanager.cxx b/vcl/unx/generic/printer/printerinfomanager.cxx
new file mode 100644
index 000000000..ae87f6adf
--- /dev/null
+++ b/vcl/unx/generic/printer/printerinfomanager.cxx
@@ -0,0 +1,892 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unx/cpdmgr.hxx>
+#include <unx/cupsmgr.hxx>
+#include <unx/gendata.hxx>
+#include <unx/helper.hxx>
+
+#include <tools/urlobj.hxx>
+#include <tools/config.hxx>
+
+#include <i18nutil/paper.hxx>
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+
+#include <osl/file.hxx>
+#include <osl/thread.hxx>
+#include <osl/mutex.hxx>
+#include <o3tl/string_view.hxx>
+
+// filename of configuration files
+constexpr OUStringLiteral PRINT_FILENAME = u"psprint.conf";
+// the group of the global defaults
+constexpr OStringLiteral GLOBAL_DEFAULTS_GROUP = "__Global_Printer_Defaults__";
+
+#include <cstddef>
+#include <unordered_set>
+
+using namespace psp;
+using namespace osl;
+
+namespace psp
+{
+ class SystemQueueInfo final : public Thread
+ {
+ mutable Mutex m_aMutex;
+ bool m_bChanged;
+ std::vector< PrinterInfoManager::SystemPrintQueue >
+ m_aQueues;
+ OUString m_aCommand;
+
+ virtual void SAL_CALL run() override;
+
+ public:
+ SystemQueueInfo();
+ virtual ~SystemQueueInfo() override;
+
+ bool hasChanged() const;
+ OUString getCommand() const;
+
+ // sets changed status to false; therefore not const
+ void getSystemQueues( std::vector< PrinterInfoManager::SystemPrintQueue >& rQueues );
+ };
+} // namespace
+
+/*
+* class PrinterInfoManager
+*/
+
+PrinterInfoManager& PrinterInfoManager::get()
+{
+ // can't move to GenericUnixSalData, because of vcl/null/printerinfomanager.cxx
+ GenericUnixSalData* pSalData = GetGenericUnixSalData();
+ PrinterInfoManager* pPIM = pSalData->m_pPrinterInfoManager.get();
+ if (pPIM)
+ return *pPIM;
+
+ pPIM = CPDManager::tryLoadCPD();
+ if (!pPIM)
+ pPIM = CUPSManager::tryLoadCUPS();
+ if (!pPIM)
+ pPIM = new PrinterInfoManager();
+ pSalData->m_pPrinterInfoManager.reset(pPIM);
+ pPIM->initialize();
+
+ SAL_INFO("vcl.unx.print", "created PrinterInfoManager of type "
+ << static_cast<int>(pPIM->getType()));
+ return *pPIM;
+}
+
+PrinterInfoManager::PrinterInfoManager( Type eType ) :
+ m_eType( eType ),
+ m_bUseIncludeFeature( false ),
+ m_bUseJobPatch( true ),
+ m_aSystemDefaultPaper( "A4" )
+{
+ if( eType == Type::Default )
+ m_pQueueInfo.reset( new SystemQueueInfo );
+
+ m_aSystemDefaultPaper = OStringToOUString(
+ PaperInfo::toPSName(PaperInfo::getSystemDefaultPaper().getPaper()),
+ RTL_TEXTENCODING_UTF8);
+}
+
+PrinterInfoManager::~PrinterInfoManager()
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "PrinterInfoManager: "
+ << "destroyed Manager of type "
+ << ((int) getType()));
+#endif
+}
+
+bool PrinterInfoManager::checkPrintersChanged( bool bWait )
+{
+ // check if files were created, deleted or modified since initialize()
+ bool bChanged = false;
+ for (auto const& watchFile : m_aWatchFiles)
+ {
+ DirectoryItem aItem;
+ if( DirectoryItem::get( watchFile.m_aFilePath, aItem ) )
+ {
+ if( watchFile.m_aModified.Seconds != 0 )
+ {
+ bChanged = true; // file probably has vanished
+ break;
+ }
+ }
+ else
+ {
+ FileStatus aStatus( osl_FileStatus_Mask_ModifyTime );
+ if( aItem.getFileStatus( aStatus ) )
+ {
+ bChanged = true; // unlikely but not impossible
+ break;
+ }
+ else
+ {
+ TimeValue aModified = aStatus.getModifyTime();
+ if( aModified.Seconds != watchFile.m_aModified.Seconds )
+ {
+ bChanged = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if( bWait && m_pQueueInfo )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "syncing printer discovery thread.");
+#endif
+ m_pQueueInfo->join();
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "done: syncing printer discovery thread.");
+#endif
+ }
+
+ if( ! bChanged && m_pQueueInfo )
+ bChanged = m_pQueueInfo->hasChanged();
+ if( bChanged )
+ {
+ initialize();
+ }
+
+ return bChanged;
+}
+
+void PrinterInfoManager::initialize()
+{
+ m_bUseIncludeFeature = false;
+ m_aPrinters.clear();
+ m_aWatchFiles.clear();
+ OUString aDefaultPrinter;
+
+ // first initialize the global defaults
+ // have to iterate over all possible files
+ // there should be only one global setup section in all
+ // available config files
+ m_aGlobalDefaults = PrinterInfo();
+
+ // need a parser for the PPDContext. generic printer should do.
+ m_aGlobalDefaults.m_pParser = PPDParser::getParser( "SGENPRT" );
+ m_aGlobalDefaults.m_aContext.setParser( m_aGlobalDefaults.m_pParser );
+
+ if( ! m_aGlobalDefaults.m_pParser )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "Error: no default PPD file "
+ << "SGENPRT available, shutting down psprint...");
+#endif
+ return;
+ }
+
+ std::vector< OUString > aDirList;
+ psp::getPrinterPathList( aDirList, nullptr );
+ for (auto const& printDir : aDirList)
+ {
+ INetURLObject aFile( printDir, INetProtocol::File, INetURLObject::EncodeMechanism::All );
+ aFile.Append( PRINT_FILENAME );
+ Config aConfig( aFile.PathToFileName() );
+ if( aConfig.HasGroup( GLOBAL_DEFAULTS_GROUP ) )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "found global defaults in "
+ << aFile.PathToFileName());
+#endif
+ aConfig.SetGroup( GLOBAL_DEFAULTS_GROUP );
+
+ OString aValue( aConfig.ReadKey( "Copies" ) );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nCopies = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "Orientation" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_eOrientation = aValue.equalsIgnoreAsciiCase("Landscape") ? orientation::Landscape : orientation::Portrait;
+
+ aValue = aConfig.ReadKey( "MarginAdjust" );
+ if (!aValue.isEmpty())
+ {
+ sal_Int32 nIdx {0};
+ m_aGlobalDefaults.m_nLeftMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ m_aGlobalDefaults.m_nRightMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ m_aGlobalDefaults.m_nTopMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ m_aGlobalDefaults.m_nBottomMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ }
+
+ aValue = aConfig.ReadKey( "ColorDepth", "24" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nColorDepth = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "ColorDevice" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nColorDevice = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "PSLevel" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nPSLevel = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "PDFDevice" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nPDFDevice = aValue.toInt32();
+
+ // get the PPDContext of global JobData
+ for( int nKey = 0; nKey < aConfig.GetKeyCount(); ++nKey )
+ {
+ OString aKey( aConfig.GetKeyName( nKey ) );
+ if (aKey.startsWith("PPD_"))
+ {
+ aValue = aConfig.ReadKey( aKey );
+ const PPDKey* pKey = m_aGlobalDefaults.m_pParser->getKey(OStringToOUString(aKey.subView(4), RTL_TEXTENCODING_ISO_8859_1));
+ if( pKey )
+ {
+ m_aGlobalDefaults.m_aContext.
+ setValue( pKey,
+ aValue == "*nil" ? nullptr : pKey->getValue(OStringToOUString(aValue, RTL_TEXTENCODING_ISO_8859_1)),
+ true );
+ }
+ }
+ }
+ }
+ }
+ setDefaultPaper( m_aGlobalDefaults.m_aContext );
+
+ // now collect all available printers
+ for (auto const& printDir : aDirList)
+ {
+ INetURLObject aDir( printDir, INetProtocol::File, INetURLObject::EncodeMechanism::All );
+ INetURLObject aFile( aDir );
+ aFile.Append( PRINT_FILENAME );
+
+ // check directory validity
+ OUString aUniPath;
+ FileBase::getFileURLFromSystemPath( aDir.PathToFileName(), aUniPath );
+ Directory aDirectory( aUniPath );
+ if( aDirectory.open() )
+ continue;
+ aDirectory.close();
+
+ FileBase::getFileURLFromSystemPath( aFile.PathToFileName(), aUniPath );
+ FileStatus aStatus( osl_FileStatus_Mask_ModifyTime );
+ DirectoryItem aItem;
+
+ // setup WatchFile list
+ WatchFile aWatchFile;
+ aWatchFile.m_aFilePath = aUniPath;
+ if( ! DirectoryItem::get( aUniPath, aItem ) &&
+ ! aItem.getFileStatus( aStatus ) )
+ {
+ aWatchFile.m_aModified = aStatus.getModifyTime();
+ }
+ else
+ {
+ aWatchFile.m_aModified.Seconds = 0;
+ aWatchFile.m_aModified.Nanosec = 0;
+ }
+ m_aWatchFiles.push_back( aWatchFile );
+
+ Config aConfig( aFile.PathToFileName() );
+ for( int nGroup = 0; nGroup < aConfig.GetGroupCount(); nGroup++ )
+ {
+ aConfig.SetGroup( aConfig.GetGroupName( nGroup ) );
+ OString aValue = aConfig.ReadKey( "Printer" );
+ if (!aValue.isEmpty())
+ {
+ OUString aPrinterName;
+
+ sal_Int32 nNamePos = aValue.indexOf('/');
+ // check for valid value of "Printer"
+ if (nNamePos == -1)
+ continue;
+
+ Printer aPrinter;
+ // initialize to global defaults
+ aPrinter.m_aInfo = m_aGlobalDefaults;
+
+ aPrinterName = OStringToOUString(aValue.subView(nNamePos+1),
+ RTL_TEXTENCODING_UTF8);
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+ aPrinter.m_aInfo.m_aDriverName = OStringToOUString(aValue.subView(0, nNamePos), RTL_TEXTENCODING_UTF8);
+
+ // set parser, merge settings
+ // don't do this for CUPS printers as this is done
+ // by the CUPS system itself
+ if( !aPrinter.m_aInfo.m_aDriverName.startsWith( "CUPS:" ) )
+ {
+ aPrinter.m_aInfo.m_pParser = PPDParser::getParser( aPrinter.m_aInfo.m_aDriverName );
+ aPrinter.m_aInfo.m_aContext.setParser( aPrinter.m_aInfo.m_pParser );
+ // note: setParser also purges the context
+
+ // ignore this printer if its driver is not found
+ if( ! aPrinter.m_aInfo.m_pParser )
+ continue;
+
+ // merge the ppd context keys if the printer has the same keys and values
+ // this is a bit tricky, since it involves mixing two PPDs
+ // without constraints which might end up badly
+ // this feature should be use with caution
+ // it is mainly to select default paper sizes for new printers
+ for( std::size_t nPPDValueModified = 0; nPPDValueModified < m_aGlobalDefaults.m_aContext.countValuesModified(); nPPDValueModified++ )
+ {
+ const PPDKey* pDefKey = m_aGlobalDefaults.m_aContext.getModifiedKey( nPPDValueModified );
+ const PPDValue* pDefValue = m_aGlobalDefaults.m_aContext.getValue( pDefKey );
+ const PPDKey* pPrinterKey = pDefKey ? aPrinter.m_aInfo.m_pParser->getKey( pDefKey->getKey() ) : nullptr;
+ if( pDefKey && pPrinterKey )
+ // at least the options exist in both PPDs
+ {
+ if( pDefValue )
+ {
+ const PPDValue* pPrinterValue = pPrinterKey->getValue( pDefValue->m_aOption );
+ if( pPrinterValue )
+ // the printer has a corresponding option for the key
+ aPrinter.m_aInfo.m_aContext.setValue( pPrinterKey, pPrinterValue );
+ }
+ else
+ aPrinter.m_aInfo.m_aContext.setValue( pPrinterKey, nullptr );
+ }
+ }
+
+ aValue = aConfig.ReadKey( "Command" );
+ // no printer without a command
+ if (aValue.isEmpty())
+ {
+ /* TODO:
+ * porters: please append your platform to the Solaris
+ * case if your platform has SystemV printing per default.
+ */
+ #if defined __sun
+ aValue = "lp";
+ #else
+ aValue = "lpr";
+ #endif
+ }
+ aPrinter.m_aInfo.m_aCommand = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+ }
+
+ aValue = aConfig.ReadKey( "QuickCommand" );
+ aPrinter.m_aInfo.m_aQuickCommand = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ aValue = aConfig.ReadKey( "Features" );
+ aPrinter.m_aInfo.m_aFeatures = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ // override the settings in m_aGlobalDefaults if keys exist
+ aValue = aConfig.ReadKey( "DefaultPrinter" );
+ if (aValue != "0" && !aValue.equalsIgnoreAsciiCase("false"))
+ aDefaultPrinter = aPrinterName;
+
+ aValue = aConfig.ReadKey( "Location" );
+ aPrinter.m_aInfo.m_aLocation = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ aValue = aConfig.ReadKey( "Comment" );
+ aPrinter.m_aInfo.m_aComment = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ aValue = aConfig.ReadKey( "Copies" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nCopies = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "Orientation" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_eOrientation = aValue.equalsIgnoreAsciiCase("Landscape") ? orientation::Landscape : orientation::Portrait;
+
+ aValue = aConfig.ReadKey( "MarginAdjust" );
+ if (!aValue.isEmpty())
+ {
+ sal_Int32 nIdx {0};
+ aPrinter.m_aInfo.m_nLeftMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ aPrinter.m_aInfo.m_nRightMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ aPrinter.m_aInfo.m_nTopMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ aPrinter.m_aInfo.m_nBottomMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ }
+
+ aValue = aConfig.ReadKey( "ColorDepth" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nColorDepth = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "ColorDevice" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nColorDevice = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "PSLevel" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nPSLevel = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "PDFDevice" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nPDFDevice = aValue.toInt32();
+
+ // now iterate over all keys to extract multi key information:
+ // 1. PPDContext information
+ for( int nKey = 0; nKey < aConfig.GetKeyCount(); ++nKey )
+ {
+ OString aKey( aConfig.GetKeyName( nKey ) );
+ if( aKey.startsWith("PPD_") && aPrinter.m_aInfo.m_pParser )
+ {
+ aValue = aConfig.ReadKey( aKey );
+ const PPDKey* pKey = aPrinter.m_aInfo.m_pParser->getKey(OStringToOUString(aKey.subView(4), RTL_TEXTENCODING_ISO_8859_1));
+ if( pKey )
+ {
+ aPrinter.m_aInfo.m_aContext.
+ setValue( pKey,
+ aValue == "*nil" ? nullptr : pKey->getValue(OStringToOUString(aValue, RTL_TEXTENCODING_ISO_8859_1)),
+ true );
+ }
+ }
+ }
+
+ setDefaultPaper( aPrinter.m_aInfo.m_aContext );
+
+ // if it's a "Generic Printer", apply defaults from config...
+ aPrinter.m_aInfo.resolveDefaultBackend();
+
+ // finally insert printer
+ FileBase::getFileURLFromSystemPath( aFile.PathToFileName(), aPrinter.m_aFile );
+ std::unordered_map< OUString, Printer >::const_iterator find_it =
+ m_aPrinters.find( aPrinterName );
+ if( find_it != m_aPrinters.end() )
+ {
+ aPrinter.m_aAlternateFiles = find_it->second.m_aAlternateFiles;
+ aPrinter.m_aAlternateFiles.insert( find_it->second.m_aFile );
+ }
+ m_aPrinters[ aPrinterName ] = aPrinter;
+ }
+ }
+ }
+
+ // set default printer
+ if( !m_aPrinters.empty() )
+ {
+ if( m_aPrinters.find( aDefaultPrinter ) == m_aPrinters.end() )
+ aDefaultPrinter = m_aPrinters.begin()->first;
+ }
+ else
+ aDefaultPrinter.clear();
+ m_aDefaultPrinter = aDefaultPrinter;
+
+ if( m_eType != Type::Default )
+ return;
+
+ // add a default printer for every available print queue
+ // merge paper default printer, all else from global defaults
+ PrinterInfo aMergeInfo( m_aGlobalDefaults );
+ aMergeInfo.m_aDriverName = "SGENPRT";
+ aMergeInfo.m_aFeatures = "autoqueue";
+
+ if( !m_aDefaultPrinter.isEmpty() )
+ {
+ PrinterInfo aDefaultInfo( getPrinterInfo( m_aDefaultPrinter ) );
+
+ const PPDKey* pDefKey = aDefaultInfo.m_pParser->getKey( "PageSize" );
+ const PPDKey* pMergeKey = aMergeInfo.m_pParser->getKey( "PageSize" );
+ const PPDValue* pDefValue = aDefaultInfo.m_aContext.getValue( pDefKey );
+ const PPDValue* pMergeValue = pMergeKey ? pMergeKey->getValue( pDefValue->m_aOption ) : nullptr;
+ if( pMergeKey && pMergeValue )
+ aMergeInfo.m_aContext.setValue( pMergeKey, pMergeValue );
+ }
+
+ if( m_pQueueInfo && m_pQueueInfo->hasChanged() )
+ {
+ m_aSystemPrintCommand = m_pQueueInfo->getCommand();
+ m_pQueueInfo->getSystemQueues( m_aSystemPrintQueues );
+ m_pQueueInfo.reset();
+ }
+ for (auto const& printQueue : m_aSystemPrintQueues)
+ {
+ OUString aPrinterName = "<" + printQueue.m_aQueue + ">";
+
+ if( m_aPrinters.find( aPrinterName ) != m_aPrinters.end() )
+ // probably user made this one permanent
+ continue;
+
+ OUString aCmd( m_aSystemPrintCommand );
+ aCmd = aCmd.replaceAll( "(PRINTER)", printQueue.m_aQueue );
+
+ Printer aPrinter;
+
+ // initialize to merged defaults
+ aPrinter.m_aInfo = aMergeInfo;
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+ aPrinter.m_aInfo.m_aCommand = aCmd;
+ aPrinter.m_aInfo.m_aComment = printQueue.m_aComment;
+ aPrinter.m_aInfo.m_aLocation = printQueue.m_aLocation;
+
+ m_aPrinters[ aPrinterName ] = aPrinter;
+ }
+}
+
+void PrinterInfoManager::listPrinters( ::std::vector< OUString >& rVector ) const
+{
+ rVector.clear();
+ for (auto const& printer : m_aPrinters)
+ rVector.push_back(printer.first);
+}
+
+const PrinterInfo& PrinterInfoManager::getPrinterInfo( const OUString& rPrinter ) const
+{
+ static PrinterInfo aEmptyInfo;
+ std::unordered_map< OUString, Printer >::const_iterator it = m_aPrinters.find( rPrinter );
+
+ SAL_WARN_IF( it == m_aPrinters.end(), "vcl", "Do not ask for info about nonexistent printers" );
+
+ return it != m_aPrinters.end() ? it->second.m_aInfo : aEmptyInfo;
+}
+
+bool PrinterInfoManager::checkFeatureToken( const OUString& rPrinterName, const char* pToken ) const
+{
+ const PrinterInfo& rPrinterInfo( getPrinterInfo( rPrinterName ) );
+ sal_Int32 nIndex = 0;
+ while( nIndex != -1 )
+ {
+ OUString aOuterToken = rPrinterInfo.m_aFeatures.getToken( 0, ',', nIndex );
+ if( aOuterToken.getToken( 0, '=' ).equalsIgnoreAsciiCaseAscii( pToken ) )
+ return true;
+ }
+ return false;
+}
+
+FILE* PrinterInfoManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
+{
+ const PrinterInfo& rPrinterInfo = getPrinterInfo (rPrintername);
+ const OUString& rCommand = (bQuickCommand && !rPrinterInfo.m_aQuickCommand.isEmpty() ) ?
+ rPrinterInfo.m_aQuickCommand : rPrinterInfo.m_aCommand;
+ OString aShellCommand = OUStringToOString (rCommand, RTL_TEXTENCODING_ISO_8859_1) +
+ " 2>/dev/null";
+
+ return popen (aShellCommand.getStr(), "w");
+}
+
+bool PrinterInfoManager::endSpool( const OUString& /*rPrintername*/, const OUString& /*rJobTitle*/, FILE* pFile, const JobData& /*rDocumentJobData*/, bool /*bBanner*/, const OUString& /*rFaxNumber*/ )
+{
+ return (0 == pclose( pFile ));
+}
+
+void PrinterInfoManager::setupJobContextData( JobData& rData )
+{
+ std::unordered_map< OUString, Printer >::iterator it =
+ m_aPrinters.find( rData.m_aPrinterName );
+ if( it != m_aPrinters.end() )
+ {
+ rData.m_pParser = it->second.m_aInfo.m_pParser;
+ rData.m_aContext = it->second.m_aInfo.m_aContext;
+ }
+}
+
+void PrinterInfoManager::setDefaultPaper( PPDContext& rContext ) const
+{
+ if( ! rContext.getParser() )
+ return;
+
+ const PPDKey* pPageSizeKey = rContext.getParser()->getKey( "PageSize" );
+ if( ! pPageSizeKey )
+ return;
+
+ std::size_t nModified = rContext.countValuesModified();
+ auto set = false;
+ for (std::size_t i = 0; i != nModified; ++i) {
+ if (rContext.getModifiedKey(i) == pPageSizeKey) {
+ set = true;
+ break;
+ }
+ }
+
+ if( set ) // paper was set already, do not modify
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.unx.print", "not setting default paper, already set "
+ << rContext.getValue( pPageSizeKey )->m_aOption);
+#endif
+ return;
+ }
+
+ // paper not set, fill in default value
+ const PPDValue* pPaperVal = nullptr;
+ int nValues = pPageSizeKey->countValues();
+ for( int i = 0; i < nValues && ! pPaperVal; i++ )
+ {
+ const PPDValue* pVal = pPageSizeKey->getValue( i );
+ if( pVal->m_aOption.equalsIgnoreAsciiCase( m_aSystemDefaultPaper ) )
+ pPaperVal = pVal;
+ }
+ if( pPaperVal )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "setting default paper "
+ << pPaperVal->m_aOption);
+#endif
+ rContext.setValue( pPageSizeKey, pPaperVal );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "-> got paper "
+ << rContext.getValue( pPageSizeKey )->m_aOption);
+#endif
+ }
+}
+
+SystemQueueInfo::SystemQueueInfo() :
+ m_bChanged( false )
+{
+ create();
+}
+
+SystemQueueInfo::~SystemQueueInfo()
+{
+ static const char* pNoSyncDetection = getenv( "SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION" );
+ if( ! pNoSyncDetection || !*pNoSyncDetection )
+ join();
+ else
+ terminate();
+}
+
+bool SystemQueueInfo::hasChanged() const
+{
+ MutexGuard aGuard( m_aMutex );
+ bool bChanged = m_bChanged;
+ return bChanged;
+}
+
+void SystemQueueInfo::getSystemQueues( std::vector< PrinterInfoManager::SystemPrintQueue >& rQueues )
+{
+ MutexGuard aGuard( m_aMutex );
+ rQueues = m_aQueues;
+ m_bChanged = false;
+}
+
+OUString SystemQueueInfo::getCommand() const
+{
+ MutexGuard aGuard( m_aMutex );
+ OUString aRet = m_aCommand;
+ return aRet;
+}
+
+namespace {
+
+struct SystemCommandParameters;
+
+}
+
+typedef void(* tokenHandler)(const std::vector< OString >&,
+ std::vector< PrinterInfoManager::SystemPrintQueue >&,
+ const SystemCommandParameters*);
+
+namespace {
+
+struct SystemCommandParameters
+{
+ const char* pQueueCommand;
+ const char* pPrintCommand;
+ const char* pForeToken;
+ const char* pAftToken;
+ unsigned int nForeTokenCount;
+ tokenHandler pHandler;
+};
+
+}
+
+#if ! (defined(LINUX) || defined(NETBSD) || defined(FREEBSD) || defined(OPENBSD))
+static void lpgetSysQueueTokenHandler(
+ const std::vector< OString >& i_rLines,
+ std::vector< PrinterInfoManager::SystemPrintQueue >& o_rQueues,
+ const SystemCommandParameters* )
+{
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ std::unordered_set< OUString > aUniqueSet;
+ std::unordered_set< OUString > aOnlySet;
+ aUniqueSet.insert( OUString( "_all" ) );
+ aUniqueSet.insert( OUString( "_default" ) );
+
+ // the eventual "all" attribute of the "_all" queue tells us, which
+ // printers are to be used for this user at all
+
+ // find _all: line
+ OString aAllLine( "_all:" );
+ OString aAllAttr( "all=" );
+ auto it = std::find_if(i_rLines.begin(), i_rLines.end(),
+ [&aAllLine](const OString& rLine) { return rLine.indexOf( aAllLine, 0 ) == 0; });
+ if( it != i_rLines.end() )
+ {
+ // now find the "all" attribute
+ ++it;
+ it = std::find_if(it, i_rLines.end(),
+ [&aAllAttr](const OString& rLine) { return WhitespaceToSpace( rLine ).startsWith( aAllAttr ); });
+ if( it != i_rLines.end() )
+ {
+ // insert the comma separated entries into the set of printers to use
+ OString aClean( WhitespaceToSpace( *it ) );
+ sal_Int32 nPos = aAllAttr.getLength();
+ while( nPos != -1 )
+ {
+ OString aTok( aClean.getToken( 0, ',', nPos ) );
+ if( !aTok.isEmpty() )
+ aOnlySet.insert( OStringToOUString( aTok, aEncoding ) );
+ }
+ }
+ }
+
+ bool bInsertAttribute = false;
+ OString aDescrStr( "description=" );
+ OString aLocStr( "location=" );
+ for (auto const& line : i_rLines)
+ {
+ sal_Int32 nPos = 0;
+ // find the begin of a new printer section
+ nPos = line.indexOf( ':', 0 );
+ if( nPos != -1 )
+ {
+ OUString aSysQueue( OStringToOUString( line.copy( 0, nPos ), aEncoding ) );
+ // do not insert duplicates (e.g. lpstat tends to produce such lines)
+ // in case there was a "_all" section, insert only those printer explicitly
+ // set in the "all" attribute
+ if( aUniqueSet.find( aSysQueue ) == aUniqueSet.end() &&
+ ( aOnlySet.empty() || aOnlySet.find( aSysQueue ) != aOnlySet.end() )
+ )
+ {
+ o_rQueues.push_back( PrinterInfoManager::SystemPrintQueue() );
+ o_rQueues.back().m_aQueue = aSysQueue;
+ o_rQueues.back().m_aLocation = aSysQueue;
+ aUniqueSet.insert( aSysQueue );
+ bInsertAttribute = true;
+ }
+ else
+ bInsertAttribute = false;
+ continue;
+ }
+ if( bInsertAttribute && ! o_rQueues.empty() )
+ {
+ // look for "description" attribute, insert as comment
+ nPos = line.indexOf( aDescrStr, 0 );
+ if( nPos != -1 )
+ {
+ OString aComment( WhitespaceToSpace( line.copy(nPos+12) ) );
+ if( !aComment.isEmpty() )
+ o_rQueues.back().m_aComment = OStringToOUString(aComment, aEncoding);
+ continue;
+ }
+ // look for "location" attribute, inser as location
+ nPos = line.indexOf( aLocStr, 0 );
+ if( nPos != -1 )
+ {
+ OString aLoc( WhitespaceToSpace( line.copy(nPos+9) ) );
+ if( !aLoc.isEmpty() )
+ o_rQueues.back().m_aLocation = OStringToOUString(aLoc, aEncoding);
+ continue;
+ }
+ }
+ }
+}
+#endif
+static void standardSysQueueTokenHandler(
+ const std::vector< OString >& i_rLines,
+ std::vector< PrinterInfoManager::SystemPrintQueue >& o_rQueues,
+ const SystemCommandParameters* i_pParms)
+{
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ std::unordered_set< OUString > aUniqueSet;
+ OString aForeToken( i_pParms->pForeToken );
+ OString aAftToken( i_pParms->pAftToken );
+ /* Normal Unix print queue discovery, also used for Darwin 5 LPR printing
+ */
+ for (auto const& line : i_rLines)
+ {
+ sal_Int32 nPos = 0;
+
+ // search for a line describing a printer:
+ // find if there are enough tokens before the name
+ for( unsigned int i = 0; i < i_pParms->nForeTokenCount && nPos != -1; i++ )
+ {
+ nPos = line.indexOf( aForeToken, nPos );
+ if( nPos != -1 && line.getLength() >= nPos+aForeToken.getLength() )
+ nPos += aForeToken.getLength();
+ }
+ if( nPos != -1 )
+ {
+ // find if there is the token after the queue
+ sal_Int32 nAftPos = line.indexOf( aAftToken, nPos );
+ if( nAftPos != -1 )
+ {
+ // get the queue name between fore and aft tokens
+ OUString aSysQueue( OStringToOUString( line.subView( nPos, nAftPos - nPos ), aEncoding ) );
+ // do not insert duplicates (e.g. lpstat tends to produce such lines)
+ if( aUniqueSet.insert( aSysQueue ).second )
+ {
+ o_rQueues.emplace_back( );
+ o_rQueues.back().m_aQueue = aSysQueue;
+ o_rQueues.back().m_aLocation = aSysQueue;
+ }
+ }
+ }
+ }
+}
+
+const struct SystemCommandParameters aParms[] =
+{
+ #if defined(LINUX) || defined(NETBSD) || defined(FREEBSD) || defined(OPENBSD)
+ { "/usr/sbin/lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler },
+ { "lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler },
+ { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpstat -s", "lp -d \"(PRINTER)\"", "system for ", ": ", 1, standardSysQueueTokenHandler }
+ #else
+ { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpget list", "lp -d \"(PRINTER)\"", "", ":", 0, lpgetSysQueueTokenHandler },
+ { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpstat -s", "lp -d \"(PRINTER)\"", "system for ", ": ", 1, standardSysQueueTokenHandler },
+ { "/usr/sbin/lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler },
+ { "lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler }
+ #endif
+};
+
+void SystemQueueInfo::run()
+{
+ osl_setThreadName("LPR psp::SystemQueueInfo");
+
+ char pBuffer[1024];
+ std::vector< OString > aLines;
+
+ /* Discover which command we can use to get a list of all printer queues */
+ for(const auto & rParm : aParms)
+ {
+ aLines.clear();
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "trying print queue command \""
+ << rParm.pQueueCommand
+ << "\" ...");
+#endif
+ OString aCmdLine = rParm.pQueueCommand + OString::Concat(" 2>/dev/null");
+ FILE *pPipe;
+ if( (pPipe = popen( aCmdLine.getStr(), "r" )) )
+ {
+ while( fgets( pBuffer, 1024, pPipe ) )
+ aLines.emplace_back( pBuffer );
+ if( ! pclose( pPipe ) )
+ {
+ std::vector< PrinterInfoManager::SystemPrintQueue > aSysPrintQueues;
+ rParm.pHandler( aLines, aSysPrintQueues, &rParm );
+ MutexGuard aGuard( m_aMutex );
+ m_bChanged = true;
+ m_aQueues = aSysPrintQueues;
+ m_aCommand = OUString::createFromAscii( rParm.pPrintCommand );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "printing queue command: success.");
+#endif
+ break;
+ }
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "printing queue command: failed.");
+#endif
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */