diff options
Diffstat (limited to '')
21 files changed, 12507 insertions, 0 deletions
diff --git a/vcl/unx/generic/print/bitmap_gfx.cxx b/vcl/unx/generic/print/bitmap_gfx.cxx new file mode 100644 index 000000000..b559b9b13 --- /dev/null +++ b/vcl/unx/generic/print/bitmap_gfx.cxx @@ -0,0 +1,696 @@ +/* -*- 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() +{ +} + +/* virtual base class */ + +namespace { + +class ByteEncoder +{ +private: + +public: + + virtual void EncodeByte (sal_uInt8 nByte) = 0; + virtual ~ByteEncoder () = 0; +}; + +} + +ByteEncoder::~ByteEncoder() +{ +} + +/* HexEncoder */ + +namespace { + +class HexEncoder : public ByteEncoder +{ +private: + + osl::File* mpFile; + sal_uInt32 mnColumn; + sal_uInt32 mnOffset; + OStringBuffer mpFileBuffer; + +public: + + explicit HexEncoder (osl::File* pFile); + virtual ~HexEncoder () override; + void WriteAscii (sal_uInt8 nByte); + virtual void EncodeByte (sal_uInt8 nByte) override; + 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 : public ByteEncoder +{ +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 () override; + virtual void EncodeByte (sal_uInt8 nByte) override; + 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> + mpTable; // 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), + mpPrefix(nullptr), + mnTableSize(gnEOICode + 1), + mnCodeSize(gnDataSize + 1), + mnOffset(32), // free bits in dwShift + mdwShift(0) +{ + for (sal_uInt32 i = 0; i < 4096; i++) + { + mpTable[i].mpBrother = nullptr; + mpTable[i].mpFirstChild = nullptr; + mpTable[i].mnCode = i; + mpTable[i].mnValue = static_cast<sal_uInt8>(mpTable[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 = mpTable.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++) + mpTable[i].mpFirstChild = nullptr; + + mnCodeSize = gnDataSize + 1; + mnTableSize = gnEOICode + 1; + } + else + { + if(mnTableSize == static_cast<sal_uInt16>((1 << mnCodeSize) - 1)) + mnCodeSize++; + + p = mpTable.data() + (mnTableSize++); + p->mpBrother = mpPrefix->mpFirstChild; + mpPrefix->mpFirstChild = p; + p->mnValue = nV; + p->mpFirstChild = nullptr; + } + + mpPrefix = mpTable.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 + std::unique_ptr<HexEncoder> xEncoder(new HexEncoder (mpPageBody)); + + for (long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++) + { + for (long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++) + { + unsigned char nByte = rBitmap.GetPixelGray (nRow, nColumn); + xEncoder->EncodeByte (nByte); + } + } + + xEncoder.reset(); + + 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()); + + std::unique_ptr<ByteEncoder> xEncoder(new LZWEncoder(mpPageBody)); + for (sal_uInt32 i = 0; i < nSize; i++) + { + PrinterColor aColor = rBitmap.GetPaletteColor(i); + + xEncoder->EncodeByte (aColor.GetRed()); + xEncoder->EncodeByte (aColor.GetGreen()); + xEncoder->EncodeByte (aColor.GetBlue()); + } + xEncoder.reset(); + + 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); + + std::unique_ptr<ByteEncoder> xEncoder(new LZWEncoder(mpPageBody)); + + for (long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++) + { + for (long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++) + { + unsigned char nByte = rBitmap.GetPixelGray (nRow, nColumn); + xEncoder->EncodeByte (nByte); + } + } +} + +void +PrinterGfx::DrawPS2MonoImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea) +{ + writePS2Colorspace(rBitmap, psp::ImageType::MonochromeImage); + writePS2ImageHeader(rArea, psp::ImageType::MonochromeImage); + + std::unique_ptr<ByteEncoder> xEncoder(new LZWEncoder(mpPageBody)); + + for (long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++) + { + long nBitPos = 0; + unsigned char nByte = 0; + + for (long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++) + { + unsigned char nBit = rBitmap.GetPixelIdx (nRow, nColumn); + nByte |= nBit << (7 - nBitPos); + + if (++nBitPos == 8) + { + xEncoder->EncodeByte (nByte); + nBitPos = 0; + nByte = 0; + } + } + // keep the row byte aligned + if (nBitPos != 0) + xEncoder->EncodeByte (nByte); + } +} + +void +PrinterGfx::DrawPS2PaletteImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea) +{ + writePS2Colorspace(rBitmap, psp::ImageType::PaletteImage); + writePS2ImageHeader(rArea, psp::ImageType::PaletteImage); + + std::unique_ptr<ByteEncoder> xEncoder(new LZWEncoder(mpPageBody)); + + for (long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++) + { + for (long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++) + { + unsigned char nByte = rBitmap.GetPixelIdx (nRow, nColumn); + xEncoder->EncodeByte (nByte); + } + } +} + +void +PrinterGfx::DrawPS2TrueColorImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea) +{ + writePS2Colorspace(rBitmap, psp::ImageType::TrueColorImage); + writePS2ImageHeader(rArea, psp::ImageType::TrueColorImage); + + std::unique_ptr<ByteEncoder> xEncoder(new LZWEncoder(mpPageBody)); + + for (long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++) + { + for (long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++) + { + PrinterColor aColor = rBitmap.GetPixelRGB (nRow, nColumn); + xEncoder->EncodeByte (aColor.GetRed()); + xEncoder->EncodeByte (aColor.GetGreen()); + xEncoder->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..37aa3cbe2 --- /dev/null +++ b/vcl/unx/generic/print/common_gfx.cxx @@ -0,0 +1,1148 @@ +/* -*- 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> + +using namespace psp ; + +static 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; + + 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.TopLeft().X(), pRect); + psp::appendStr (" ", pRect); + psp::getValueOf (rRectangle.TopLeft().Y(), 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 ) + { + PSSetColor (maLineColor); + PSSetColor (); + PSSetLineWidth (); + + snprintf(pString, nBezString, "%li %li moveto\n", pPath[0].X(), 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, "%li %li lineto\n", pPath[i].X(), 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, "%li %li %li %li %li %li curveto\n", + pPath[i].X(), pPath[i].Y(), + pPath[i+1].X(), pPath[i+1].Y(), + pPath[i+2].X(), 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, "%li %li moveto\n", pPath[0].X(), 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, "%li %li lineto\n", pPath[i].X(), 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, "%li %li %li %li %li %li curveto\n", + pPath[i].X(), pPath[i].Y(), + pPath[i+1].X(), pPath[i+1].Y(), + pPath[i+2].X(), 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, "%li %li moveto\n", pPtAry[i][0].X(), 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, "%li %li lineto\n", pPtAry[i][j].X(), 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, "%li %li %li %li %li %li curveto\n", + pPtAry[i][j].X(), pPtAry[i][j].Y(), + pPtAry[i][j+1].X(), pPtAry[i][j+1].Y(), + pPtAry[i][j+2].X(), 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 ) + { + 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 (sal_Int32 nAngle) +{ + sal_Int32 nPostScriptAngle = -nAngle; + 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 != 0) + { + 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 != 0) + 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( aLine.getToken(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.copy( 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..3287e365f --- /dev/null +++ b/vcl/unx/generic/print/genprnpsp.cxx @@ -0,0 +1,1300 @@ +/* -*- 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. + */ + +// 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/print.hxx> +#include <vcl/pdfwriter.hxx> +#include <printerinfomanager.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/weld.hxx> +#include <strings.hrc> +#include <saldatabasic.hxx> +#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) + { + vcl::Window* pWin = Application::GetDefDialogParent(); + QueryString aQuery(pWin ? pWin->GetFrameWeld() : nullptr, 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( const OUString& 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 ) + { + 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 ) + { + 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, + long& rOutWidth, 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"); + GetSalData()->m_pInstance->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 ); + } + } + GetSalData()->m_pInstance->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 long nRotatedWidth = rComp.maPageSize.Height(); + const 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 + bool bSinglePrintJobs = false; + beans::PropertyValue* pSingleValue = i_rController.getValue( "PrintCollateAsSingleJobs" ); + if( pSingleValue ) + { + pSingleValue->Value >>= bSinglePrintJobs; + } + + int nCopies = i_rController.getPrinter()->GetCopyCount(); + bool bCollate = i_rController.getPrinter()->IsCollateCopy(); + + // 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 = static_cast<SalGenericInstance *>( GetSalData()->m_pInstance ); + 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..235f45eb8 --- /dev/null +++ b/vcl/unx/generic/print/genpspgraphics.cxx @@ -0,0 +1,1006 @@ +/* -*- 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 <vcl/bitmapaccess.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 <PhysicalFontCollection.hxx> +#include <PhysicalFontFace.hxx> +#include <salbmp.hxx> +#include <sallayout.hxx> + +using namespace psp; + +// ----- 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; +}; + + bool Bitmap32IsPreMultipled() + { + auto pBackendCapabilities = ImplGetSVData()->mpDefInst->GetBackendCapabilities(); + return pBackendCapabilities->mbSupportsBitmap32; + } +} + +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 + switch( RemoveScanline( mpBmpBuffer->mnFormat ) ) + { + case ScanlineFormat::N1BitMsbPal: + mpFncGetPixel = BitmapReadAccess::GetPixelForN1BitMsbPal; break; + case ScanlineFormat::N1BitLsbPal: + mpFncGetPixel = BitmapReadAccess::GetPixelForN1BitLsbPal; break; + case ScanlineFormat::N4BitMsnPal: + mpFncGetPixel = BitmapReadAccess::GetPixelForN4BitMsnPal; break; + case ScanlineFormat::N4BitLsnPal: + mpFncGetPixel = BitmapReadAccess::GetPixelForN4BitLsnPal; break; + case ScanlineFormat::N8BitPal: + mpFncGetPixel = BitmapReadAccess::GetPixelForN8BitPal; break; + case ScanlineFormat::N8BitTcMask: + mpFncGetPixel = BitmapReadAccess::GetPixelForN8BitTcMask; break; + case ScanlineFormat::N24BitTcBgr: + mpFncGetPixel = BitmapReadAccess::GetPixelForN24BitTcBgr; break; + case ScanlineFormat::N24BitTcRgb: + mpFncGetPixel = BitmapReadAccess::GetPixelForN24BitTcRgb; break; + case ScanlineFormat::N32BitTcAbgr: + if (Bitmap32IsPreMultipled()) + mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcAbgr; + else + mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcXbgr; + break; + case ScanlineFormat::N32BitTcArgb: + if (Bitmap32IsPreMultipled()) + mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcArgb; + else + mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcXrgb; + break; + case ScanlineFormat::N32BitTcBgra: + if (Bitmap32IsPreMultipled()) + mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcBgra; + else + mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcBgrx; + break; + case ScanlineFormat::N32BitTcRgba: + if (Bitmap32IsPreMultipled()) + mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcRgba; + else + mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcRgbx; + break; + case ScanlineFormat::N32BitTcMask: + mpFncGetPixel = BitmapReadAccess::GetPixelForN32BitTcMask; break; + default: + OSL_FAIL("Error: SalPrinterBmp::SalPrinterBmp() unknown bitmap format"); + mpFncGetPixel = nullptr; + break; + } +} + +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; +} + +/******************************************************* + * GenPspGraphics * + *******************************************************/ +GenPspGraphics::GenPspGraphics() + : m_pJobData( nullptr ), + m_pPrinterGfx( nullptr ) +{ +} + +void GenPspGraphics::Init(psp::JobData* pJob, psp::PrinterGfx* 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; + } +} + +sal_uInt16 GenPspGraphics::GetBitCount() const +{ + return m_pPrinterGfx->GetBitCount(); +} + +long GenPspGraphics::GetGraphicsWidth() const +{ + return 0; +} + +void GenPspGraphics::ResetClipRegion() +{ + m_pPrinterGfx->ResetClipRegion(); +} + +bool GenPspGraphics::setClipRegion( const vcl::Region& i_rClip ) +{ + // TODO: support polygonal clipregions here + RectangleVector aRectangles; + i_rClip.GetRegionRectangles(aRectangles); + m_pPrinterGfx->BeginSetClipRegion(); + + for (auto const& rectangle : aRectangles) + { + const long nW(rectangle.GetWidth()); + + if(nW) + { + const long nH(rectangle.GetHeight()); + + if(nH) + { + m_pPrinterGfx->UnionClipRegion( + rectangle.Left(), + rectangle.Top(), + nW, + nH); + } + } + } + + m_pPrinterGfx->EndSetClipRegion(); + + //m_pPrinterGfx->BeginSetClipRegion( i_rClip.GetRectCount() ); + + //ImplRegionInfo aInfo; + //long nX, nY, nW, nH; + //bool bRegionRect = i_rClip.ImplGetFirstRect(aInfo, nX, nY, nW, nH ); + //while( bRegionRect ) + //{ + // if ( nW && nH ) + // { + // m_pPrinterGfx->UnionClipRegion( nX, nY, nW, nH ); + // } + // bRegionRect = i_rClip.ImplGetNextRect( aInfo, nX, nY, nW, nH ); + //} + //m_pPrinterGfx->EndSetClipRegion(); + return true; +} + +void GenPspGraphics::SetLineColor() +{ + m_pPrinterGfx->SetLineColor (); +} + +void GenPspGraphics::SetLineColor( Color nColor ) +{ + psp::PrinterColor aColor (nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue()); + m_pPrinterGfx->SetLineColor (aColor); +} + +void GenPspGraphics::SetFillColor() +{ + m_pPrinterGfx->SetFillColor (); +} + +void GenPspGraphics::SetFillColor( Color nColor ) +{ + psp::PrinterColor aColor (nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue()); + m_pPrinterGfx->SetFillColor (aColor); +} + +void GenPspGraphics::SetROPLineColor( SalROPColor ) +{ + SAL_WARN( "vcl", "Error: PrinterGfx::SetROPLineColor() not implemented" ); +} + +void GenPspGraphics::SetROPFillColor( SalROPColor ) +{ + SAL_WARN( "vcl", "Error: PrinterGfx::SetROPFillColor() not implemented" ); +} + +void GenPspGraphics::SetXORMode( bool bSet, bool ) +{ + SAL_WARN_IF( bSet, "vcl", "Error: PrinterGfx::SetXORMode() not implemented" ); +} + +void GenPspGraphics::drawPixel( long nX, long nY ) +{ + m_pPrinterGfx->DrawPixel (Point(nX, nY)); +} + +void GenPspGraphics::drawPixel( long nX, long nY, Color nColor ) +{ + psp::PrinterColor aColor (nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue()); + m_pPrinterGfx->DrawPixel (Point(nX, nY), aColor); +} + +void GenPspGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 ) +{ + m_pPrinterGfx->DrawLine (Point(nX1, nY1), Point(nX2, nY2)); +} + +void GenPspGraphics::drawRect( long nX, long nY, long nDX, long nDY ) +{ + m_pPrinterGfx->DrawRect (tools::Rectangle(Point(nX, nY), Size(nDX, nDY))); +} + +void GenPspGraphics::drawPolyLine( sal_uInt32 nPoints, const SalPoint *pPtAry ) +{ + m_pPrinterGfx->DrawPolyLine (nPoints, reinterpret_cast<const Point *>(pPtAry)); +} + +void GenPspGraphics::drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) +{ + // Point must be equal to SalPoint! see include/vcl/salgtype.hxx + m_pPrinterGfx->DrawPolygon (nPoints, reinterpret_cast<const Point *>(pPtAry)); +} + +void GenPspGraphics::drawPolyPolygon( sal_uInt32 nPoly, + const sal_uInt32 *pPoints, + PCONSTSALPOINT *pPtAry ) +{ + m_pPrinterGfx->DrawPolyPolygon (nPoly, pPoints, reinterpret_cast<const Point**>(pPtAry)); +} + +bool GenPspGraphics::drawPolyPolygon( + const basegfx::B2DHomMatrix& /*rObjectToDevice*/, + const basegfx::B2DPolyPolygon&, + double /*fTransparency*/) +{ + // TODO: implement and advertise OutDevSupportType::B2DDraw support + return false; +} + +bool GenPspGraphics::drawPolyLine( + const basegfx::B2DHomMatrix& /* rObjectToDevice */, + const basegfx::B2DPolygon&, + double /*fTransparency*/, + double /*fLineWidth*/, + const std::vector< double >* /*pStroke*/, // MM01 + basegfx::B2DLineJoin /*eJoin*/, + css::drawing::LineCap /*eLineCap*/, + double /*fMiterMinimumAngle*/, + bool /* bPixelSnapHairline */) +{ + // TODO: a PS printer can draw B2DPolyLines almost directly + return false; +} + +bool GenPspGraphics::drawPolyLineBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry ) +{ + m_pPrinterGfx->DrawPolyLineBezier (nPoints, reinterpret_cast<Point const *>(pPtAry), pFlgAry); + return true; +} + +bool GenPspGraphics::drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry ) +{ + m_pPrinterGfx->DrawPolygonBezier (nPoints, reinterpret_cast<Point const *>(pPtAry), pFlgAry); + return true; +} + +bool GenPspGraphics::drawPolyPolygonBezier( sal_uInt32 nPoly, + const sal_uInt32* pPoints, + const SalPoint* const* pPtAry, + const PolyFlags* const* pFlgAry ) +{ + // Point must be equal to SalPoint! see include/vcl/salgtype.hxx + m_pPrinterGfx->DrawPolyPolygonBezier (nPoly, pPoints, reinterpret_cast<Point const * const *>(pPtAry), pFlgAry); + return true; +} + +void GenPspGraphics::invert( sal_uInt32, + const SalPoint*, + SalInvert ) +{ + SAL_WARN( "vcl", "Error: PrinterGfx::Invert() not implemented" ); +} + +bool GenPspGraphics::drawEPS( long nX, long nY, long nWidth, long nHeight, void* pPtr, sal_uInt32 nSize ) +{ + return m_pPrinterGfx->DrawEPS( tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ), pPtr, nSize ); +} + +void GenPspGraphics::copyBits( const SalTwoRect&, + SalGraphics* ) +{ + OSL_FAIL( "Error: PrinterGfx::CopyBits() not implemented" ); +} + +void GenPspGraphics::copyArea ( long,long,long,long,long,long,bool ) +{ + OSL_FAIL( "Error: PrinterGfx::CopyArea() not implemented" ); +} + +void GenPspGraphics::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 GenPspGraphics::drawBitmap( const SalTwoRect&, + const SalBitmap&, + const SalBitmap& ) +{ + OSL_FAIL("Error: no PrinterGfx::DrawBitmap() for transparent bitmap"); +} + +void GenPspGraphics::drawMask( const SalTwoRect&, + const SalBitmap &, + Color ) +{ + OSL_FAIL("Error: PrinterGfx::DrawMask() not implemented"); +} + +std::shared_ptr<SalBitmap> GenPspGraphics::getBitmap( long, long, long, long ) +{ + SAL_INFO("vcl", "Warning: PrinterGfx::GetBitmap() not implemented"); + return nullptr; +} + +Color GenPspGraphics::getPixel( long, long ) +{ + OSL_FAIL("Warning: PrinterGfx::GetPixel() not implemented"); + return 0; +} + +void GenPspGraphics::invert(long,long,long,long,SalInvert) +{ + OSL_FAIL("Warning: PrinterGfx::Invert() not implemented"); +} + +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; + Point aPos; + int nStart = 0; + while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart)) + m_pPrinterGfx->DrawGlyph(aPos, *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 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; + } + int nWeight = static_cast<int>(rEntry.GetWeight()); + int nRealWeight = static_cast<int>(m_pPrinterGfx->GetFontMgr().getFontWeight( nID )); + if( nRealWeight <= int(WEIGHT_MEDIUM) && nWeight > int(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( PhysicalFontCollection*, const OUString&,const OUString& ) +{ + return false; +} + +bool GenPspGraphics::AddTempDevFontHelper( PhysicalFontCollection* pFontCollection, + const OUString& 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 ); + 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( 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 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 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( 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( aFileName.copy( nPos+1, 3 ).equalsIgnoreAsciiCase( pLangBoost ) ) + nQuality += 10; + } + + rtl::Reference<ImplPspFontData> pFD(new ImplPspFontData( aInfo )); + pFD->IncreaseQualityBy( nQuality ); + pFontCollection->Add( pFD.get() ); +} + +bool GenPspGraphics::blendBitmap( const SalTwoRect&, const SalBitmap& ) +{ + return false; +} + +bool GenPspGraphics::blendAlphaBitmap( const SalTwoRect&, const SalBitmap&, const SalBitmap&, const SalBitmap& ) +{ + return false; +} + +bool GenPspGraphics::drawAlphaBitmap( const SalTwoRect&, + const SalBitmap&, + const SalBitmap& ) +{ + return false; +} + +bool GenPspGraphics::drawTransformedBitmap( + const basegfx::B2DPoint&, + const basegfx::B2DPoint&, + const basegfx::B2DPoint&, + const SalBitmap&, + const SalBitmap*) +{ + // here direct support for transformed bitmaps can be implemented + return false; +} + +bool GenPspGraphics::drawAlphaRect( long, long, long, long, sal_uInt8 ) +{ + return false; +} + +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(); +} + +SystemFontData GenPspGraphics::GetSysFontData( int /* nFallbacklevel */ ) const +{ + return SystemFontData(); +} + +#endif // ENABLE_CAIRO_CANVAS + +bool GenPspGraphics::supportsOperation( OutDevSupportType ) const +{ + return false; +} + +void GenPspGraphics::DoFreeEmbedFontData( const void* pData, long nLen ) +{ + if( pData ) + munmap( const_cast<void *>(pData), nLen ); +} + +const void* GenPspGraphics::DoGetEmbedFontData(psp::fontID aFont, 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, long nLen ) +{ + DoFreeEmbedFontData( pData, nLen ); +} + +const void* GenPspGraphics::GetEmbedFontData(const PhysicalFontFace* pFont, 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..c27348afc --- /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, const OString &rFontName) +{ + if ( nEnc == RTL_TEXTENCODING_MS_1252 + || nEnc == RTL_TEXTENCODING_ISO_8859_1) + { + return rFontName + "-iso1252"; + } + else + if (nEnc >= RTL_TEXTENCODING_USER_START && nEnc <= RTL_TEXTENCODING_USER_END) + { + return 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; + 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..33b928d75 --- /dev/null +++ b/vcl/unx/generic/print/glyphset.hxx @@ -0,0 +1,82 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_VCL_GENERIC_PRINT_GLYPHSET_HXX +#define INCLUDED_VCL_GENERIC_PRINT_GLYPHSET_HXX + +#include <osl/file.hxx> + +#include <rtl/string.hxx> +#include <vcl/glyphitem.hxx> + +#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, + const OString &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 */ + +#endif + +/* 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..de598f1f6 --- /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 (const OUString& rName, const OUString& rExtension) +{ + OUString aFile = 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, + const OUString& 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 ("psp_head", aExt); + mpJobTrailer = CreateSpoolFile ("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.copy(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 ( "psp_pghead", aExt) ); + maPageVector.push_back( CreateSpoolFile ( "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 ) +{ + 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..fae6a1552 --- /dev/null +++ b/vcl/unx/generic/print/prtsetup.cxx @@ -0,0 +1,507 @@ +/* -*- 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(OUString::number(reinterpret_cast<sal_Int64>(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(OUString::number(reinterpret_cast<sal_IntPtr>(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 ) + { + 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 = reinterpret_cast<PPDValue*>(rBox.get_active_id().toInt64()); + m_pParent->m_aJobData.m_aContext.setValue( pKey, pValue ); + update(); + } + + m_pParent->SetDataModified( true ); +} + +IMPL_LINK( RTSPaperPage, CheckBoxHdl, weld::ToggleButton&, /*cBox*/, 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.SetInvokeHandler(LINK(this, RTSDevicePage, ImplHandleReselectHdl)); + m_aReselectCustomIdle.SetDebugName("RTSDevicePage m_aReselectCustomIdle"); + + 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 ) ); + + 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 ) + { + 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(OUString::number(reinterpret_cast<sal_Int64>(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 = reinterpret_cast<PPDKey*>(m_xPPDKeyBox->get_selected_id().toInt64()); + FillValueBox( pKey ); + } + else if (&rBox == m_xPPDValueBox.get()) + { + const PPDKey* pKey = reinterpret_cast<PPDKey*>(m_xPPDKeyBox->get_selected_id().toInt64()); + const PPDValue* pValue = reinterpret_cast<PPDValue*>(m_xPPDValueBox->get_selected_id().toInt64()); + if (pKey && pValue) + { + m_pParent->m_aJobData.m_aContext.setValue( pKey, pValue ); + ValueBoxChanged(pKey); + } + } + 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(OUString::number(reinterpret_cast<sal_Int64>(pValue)), aEntry); + } + } + pValue = m_pParent->m_aJobData.m_aContext.getValue( pKey ); + m_xPPDValueBox->select_id(OUString::number(reinterpret_cast<sal_Int64>(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..e5d88c7e1 --- /dev/null +++ b/vcl/unx/generic/print/prtsetup.hxx @@ -0,0 +1,137 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_VCL_GENERIC_PRINT_PRTSETUP_HXX +#define INCLUDED_VCL_GENERIC_PRINT_PRTSETUP_HXX + +#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::ToggleButton&, 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(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); + +#endif // _PAD_PRTSETUP_HXX + +/* 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..7b667f446 --- /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 it self +% 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..91e986dbd --- /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.makeStringAndClear()); + 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, const OUString &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..1a5ec620d --- /dev/null +++ b/vcl/unx/generic/print/psputil.hxx @@ -0,0 +1,54 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_VCL_GENERIC_PRINT_PSPUTIL_HXX +#define INCLUDED_VCL_GENERIC_PRINT_PSPUTIL_HXX + +#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, const OUString &rString); + +} /* namespace psp */ + +#endif // INCLUDED_VCL_GENERIC_PRINT_PSPUTIL_HXX + +/* 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..d3153952b --- /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, + sal_Int32 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 + sal_Int32 nCurrentTextAngle = mnTextAngle; + Point aPoint( rPoint ); + + if (nCurrentTextAngle != 0) + { + PSGSave (); + PSTranslate (rPoint); + PSRotate (nCurrentTextAngle); + mnTextAngle = 0; + 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); + // 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 != 0) + { + 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..bebd56833 --- /dev/null +++ b/vcl/unx/generic/printer/cpdmgr.cxx @@ -0,0 +1,754 @@ +/* -*- 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; + GDBusNodeInfo *introspection_data; + + // Get Interface for introspection + g_file_get_contents (FRONTEND_INTERFACE, &contents, nullptr, nullptr); + 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) + { + GDBusProxy *proxy; + // Get Interface for introspection + g_file_get_contents (BACKEND_INTERFACE, &contents, nullptr, nullptr); + introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr); + 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_free(backend.second); + 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); + } +} + +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; + GDBusNodeInfo *introspection_data; + + // Get Interface for introspection + g_file_get_contents ("/usr/share/dbus-1/interfaces/org.openprinting.Backend.xml", &contents, nullptr, nullptr); + 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(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; + 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( pFile ); + } +#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..f5a50345a --- /dev/null +++ b/vcl/unx/generic/printer/cupsmgr.cxx @@ -0,0 +1,965 @@ +/* -*- 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 <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> + +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 + OString aResult = cupsGetPPD(m_aParameter.getStr()); +#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 ) + { + // if the thread is still running here, then + // cupsGetDests is hung; terminate the thread instead of joining + osl_terminateThread( 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 ) + { + 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, const OString& rServer, const OString& 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, const OString& rServer, const OString& 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.isEmpty()) + m_xUserEdit->grab_focus(); + else + { + m_xUserEdit->set_text(OStringToOUString(rUserName, osl_getThreadTextEncoding())); + m_xPassEdit->grab_focus(); + } + } + + bool AuthenticateQuery(const OString& rServer, OString& rUserName, OString& rPassword) + { + bool bRet = false; + + vcl::Window* pWin = Application::GetDefDialogParent(); + RTSPWDialog aDialog(pWin ? pWin->GetFrameWeld() : nullptr, 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 + { + OUString aToken = aInfo.m_aAuthInfoRequired.getToken(0, ',', nIndex); + if (aToken == "domain") + bDomain = true; + else if (aToken == "username") + bUser = true; + else if (aToken == "password") + bPass = true; + } + while (nIndex >= 0); + + if (bDomain || bUser || bPass) + { + OString sPrinterName(OUStringToOString(rPrintername, RTL_TEXTENCODING_UTF8)); + OString sUser = cupsUser(); + vcl::Window* pWin = Application::GetDefDialogParent(); + RTSPWDialog aDialog(pWin ? pWin->GetFrameWeld() : nullptr, 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( pFile ); + 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..362305bea --- /dev/null +++ b/vcl/unx/generic/printer/jobdata.cxx @@ -0,0 +1,306 @@ +/* -*- 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 <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 ) + { + const PPDKey* pKey = pParser->getKey( "Collate" ); + if( pKey ) + { + 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.makeStringAndClear()); + + aLine.append("orientation="); + if (m_eOrientation == orientation::Landscape) + aLine.append("Landscape"); + else + aLine.append("Portrait"); + aStream.WriteLine(aLine.makeStringAndClear()); + + aLine.append("copies="); + aLine.append(static_cast<sal_Int32>(m_nCopies)); + aStream.WriteLine(aLine.makeStringAndClear()); + + if (m_nPDFDevice > 0) + { + aLine.append("collate="); + aLine.append(OString::boolean(m_bCollate)); + aStream.WriteLine(aLine.makeStringAndClear()); + } + + aLine.append("margindajustment="); + 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.makeStringAndClear()); + + aLine.append("colordepth="); + aLine.append(static_cast<sal_Int32>(m_nColorDepth)); + aStream.WriteLine(aLine.makeStringAndClear()); + + aLine.append("pslevel="); + aLine.append(static_cast<sal_Int32>(m_nPSLevel)); + aStream.WriteLine(aLine.makeStringAndClear()); + + aLine.append("pdfdevice="); + aLine.append(static_cast<sal_Int32>(m_nPDFDevice)); + aStream.WriteLine(aLine.makeStringAndClear()); + + aLine.append("colordevice="); + aLine.append(static_cast<sal_Int32>(m_nColorDevice)); + aStream.WriteLine(aLine.makeStringAndClear()); + + // now append the PPDContext stream buffer + aStream.WriteLine( "PPDContexData" ); + 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 margindajustmentEquals[] = "margindajustment="; + 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.copy(RTL_CONSTASCII_LENGTH(printerEquals)), RTL_TEXTENCODING_UTF8); + } + else if (aLine.startsWith(orientatationEquals)) + { + bOrientation = true; + rJobData.m_eOrientation = aLine.copy(RTL_CONSTASCII_LENGTH(orientatationEquals)).equalsIgnoreAsciiCase("landscape") ? orientation::Landscape : orientation::Portrait; + } + else if (aLine.startsWith(copiesEquals)) + { + bCopies = true; + rJobData.m_nCopies = aLine.copy(RTL_CONSTASCII_LENGTH(copiesEquals)).toInt32(); + } + else if (aLine.startsWith(collateEquals)) + { + rJobData.m_bCollate = aLine.copy(RTL_CONSTASCII_LENGTH(collateEquals)).toBoolean(); + } + else if (aLine.startsWith(margindajustmentEquals)) + { + bMargin = true; + sal_Int32 nIdx {RTL_CONSTASCII_LENGTH(margindajustmentEquals)}; + rJobData.m_nLeftMarginAdjust = aLine.getToken(0, ',', nIdx).toInt32(); + rJobData.m_nRightMarginAdjust = aLine.getToken(0, ',', nIdx).toInt32(); + rJobData.m_nTopMarginAdjust = aLine.getToken(0, ',', nIdx).toInt32(); + rJobData.m_nBottomMarginAdjust = aLine.getToken(0, ',', nIdx).toInt32(); + } + else if (aLine.startsWith(colordepthEquals)) + { + bColorDepth = true; + rJobData.m_nColorDepth = aLine.copy(RTL_CONSTASCII_LENGTH(colordepthEquals)).toInt32(); + } + else if (aLine.startsWith(colordeviceEquals)) + { + bColorDevice = true; + rJobData.m_nColorDevice = aLine.copy(RTL_CONSTASCII_LENGTH(colordeviceEquals)).toInt32(); + } + else if (aLine.startsWith(pslevelEquals)) + { + bPSLevel = true; + rJobData.m_nPSLevel = aLine.copy(RTL_CONSTASCII_LENGTH(pslevelEquals)).toInt32(); + } + else if (aLine.startsWith(pdfdeviceEquals)) + { + bPDFDevice = true; + rJobData.m_nPDFDevice = aLine.copy(RTL_CONSTASCII_LENGTH(pdfdeviceEquals)).toInt32(); + } + else if (aLine == "PPDContexData" && 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..d949441db --- /dev/null +++ b/vcl/unx/generic/printer/ppdparser.cxx @@ -0,0 +1,1987 @@ +/* -*- 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 <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/mutex.hxx> +#include <osl/file.hxx> +#include <osl/process.h> +#include <osl/thread.h> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/instance.hxx> +#include <sal/log.hxx> +#include <salhelper/linkhelper.hxx> + +#include <com/sun/star/lang/Locale.hpp> + +#include <unordered_map> + +#ifdef ENABLE_CUPS +#include <cups/cups.h> +#endif + +#include <config_dbus.h> +#include <config_gio.h> +#include <boost/functional/hash.hpp> + +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; + boost::hash_combine(seed, rLocale.Language.hashCode()); + boost::hash_combine(seed, rLocale.Country.hashCode()); + boost::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::unique_ptr<std::unordered_map< OUString, OUString >> pAllPPDFiles; + }; +} + +using namespace psp; + +namespace +{ + struct thePPDCache : public rtl::Static<PPDCache, 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 */ ) + { + // so let's try to decompress the stream + mpMemStream.reset( new SvMemoryStream( 4096, 4096 ) ); + ZCodec aCodec; + aCodec.BeginCompression( ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true ); + 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 = thePPDCache::get(); + + osl::Directory aDir( rDir ); + if ( aDir.open() == osl::FileBase::E_None ) + { + 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.pAllPPDFiles)[ 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.pAllPPDFiles ) + return; + + rPPDCache.pAllPPDFiles.reset(new std::unordered_map< OUString, OUString >); + + // 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.pAllPPDFiles->find( OUString( "SGENPRT" ) ) == rPPDCache.pAllPPDFiles->end() ) + { + // 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.pAllPPDFiles->find("SGENPRT") == + rPPDCache.pAllPPDFiles->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 = thePPDCache::get(); + + 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.pAllPPDFiles->find( aBase ); + nLastIndex = aBase.lastIndexOf( '.' ); + if( nLastIndex > 0 ) + aBase = aBase.copy( 0, nLastIndex ); + } while( it == rPPDCache.pAllPPDFiles->end() && nLastIndex > 0 ); + + if( it == rPPDCache.pAllPPDFiles->end() && bRetry ) + { + // a new file ? rehash + rPPDCache.pAllPPDFiles.reset(); + bRetry = false; + // note this is optimized for office start where + // no new files occur and initPPDFiles is called only once + } + } while( ! rPPDCache.pAllPPDFiles ); + + if( it != rPPDCache.pAllPPDFiles->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 ) +{ + static ::osl::Mutex aMutex; + ::osl::Guard< ::osl::Mutex > 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 = thePPDCache::get(); + 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::stripStart(aCurLine, ' '); + aCurLine = comphelper::string::stripEnd(aCurLine, ' '); + aCurLine = comphelper::string::stripStart(aCurLine, '\t'); + aCurLine = comphelper::string::stripEnd(aCurLine, '\t'); + aCurLine = comphelper::string::stripEnd(aCurLine, '\r'); + aCurLine = comphelper::string::stripEnd(aCurLine, '\n'); + aCurLine = comphelper::string::stripStart(aCurLine, '"'); + aCurLine = comphelper::string::stripEnd(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.makeStringAndClear(), 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 const OString 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.copy( 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.copy(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.copy(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.copy(8), RTL_TEXTENCODING_MS_1252)); + sal_Int32 nPos = aKey.indexOf( ':' ); + if( nPos != -1 ) + { + aKey = aKey.copy(0, nPos); + OUString aOption(OStringToOUString( + WhitespaceToSpace(aLine.copy(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, const OString& 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, "" ); + + 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, ""); + 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, "" ); + 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( + const OUString& 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( + const OUString& 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( + const OUString& rString, + int& rXRes, int& rYRes ) +{ + rXRes = rYRes = 300; + + const sal_Int32 nDPIPos {rString.indexOf( "dpi" )}; + if( nDPIPos != -1 ) + { + const sal_Int32 nPos {rString.indexOf( 'x' )}; + if( nPos >=0 ) + { + rXRes = rString.copy( 0, nPos ).toInt32(); + rYRes = rString.copy(nPos+1, nDPIPos - nPos - 1).toInt32(); + } + else + rXRes = rYRes = rString.copy( 0, nDPIPos ).toInt32(); + } +} + +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.copy( 0, nPos ), RTL_TEXTENCODING_MS_1252 ) ); + if( pKey ) + { + const PPDValue* pValue = nullptr; + OUString aOption(OStringToOUString(aLine.copy(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 ) + { + const PPDKey* pKey = m_pParser->getKey( "PageSize" ); + if( pKey ) + { + 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..ff4b5e691 --- /dev/null +++ b/vcl/unx/generic/printer/printerinfomanager.cxx @@ -0,0 +1,900 @@ +/* -*- 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/helper.hxx> + +#include <saldatabasic.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> + +// filename of configuration files +#define PRINT_FILENAME "psprint.conf" +// the group of the global defaults +#define 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() +{ + SalData* pSalData = GetSalData(); + + if( ! pSalData->m_pPIManager ) + { + pSalData->m_pPIManager = CPDManager::tryLoadCPD(); + if( ! pSalData->m_pPIManager ) + pSalData->m_pPIManager = CUPSManager::tryLoadCUPS(); + if( ! pSalData->m_pPIManager ) + pSalData->m_pPIManager = new PrinterInfoManager(); + pSalData->m_pPIManager->initialize(); +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("vcl.unx.print", "PrinterInfoManager::get " + << "create Manager of type " + << ((int) pSalData->m_pPIManager->getType())); +#endif + } + + return *pSalData->m_pPIManager; +} + +void PrinterInfoManager::release() +{ + SalData* pSalData = GetSalData(); + delete pSalData->m_pPIManager; + pSalData->m_pPIManager = nullptr; +} + +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 = aValue.getToken(0, ',', nIdx).toInt32(); + m_aGlobalDefaults.m_nRightMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32(); + m_aGlobalDefaults.m_nTopMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32(); + m_aGlobalDefaults.m_nBottomMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32(); + } + + 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.copy(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.copy(nNamePos+1), + RTL_TEXTENCODING_UTF8); + aPrinter.m_aInfo.m_aPrinterName = aPrinterName; + aPrinter.m_aInfo.m_aDriverName = OStringToOUString(aValue.copy(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 = aValue.getToken(0, ',', nIdx).toInt32(); + aPrinter.m_aInfo.m_nRightMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32(); + aPrinter.m_aInfo.m_nTopMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32(); + aPrinter.m_aInfo.m_nBottomMarginAdjust = aValue.getToken(0, ',', nIdx).toInt32(); + } + + 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.copy(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.copy( 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; + } + } + } + } +} + +static 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 + OStringLiteral(" 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: */ |