diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /vcl/unx/generic/print | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/unx/generic/print')
-rw-r--r-- | vcl/unx/generic/print/bitmap_gfx.cxx | 696 | ||||
-rw-r--r-- | vcl/unx/generic/print/common_gfx.cxx | 1148 | ||||
-rw-r--r-- | vcl/unx/generic/print/genprnpsp.cxx | 1300 | ||||
-rw-r--r-- | vcl/unx/generic/print/genpspgraphics.cxx | 1006 | ||||
-rw-r--r-- | vcl/unx/generic/print/glyphset.cxx | 301 | ||||
-rw-r--r-- | vcl/unx/generic/print/glyphset.hxx | 82 | ||||
-rw-r--r-- | vcl/unx/generic/print/printerjob.cxx | 973 | ||||
-rw-r--r-- | vcl/unx/generic/print/prtsetup.cxx | 507 | ||||
-rw-r--r-- | vcl/unx/generic/print/prtsetup.hxx | 137 | ||||
-rw-r--r-- | vcl/unx/generic/print/psheader.ps | 363 | ||||
-rw-r--r-- | vcl/unx/generic/print/psputil.cxx | 184 | ||||
-rw-r--r-- | vcl/unx/generic/print/psputil.hxx | 54 | ||||
-rw-r--r-- | vcl/unx/generic/print/text_gfx.cxx | 158 |
13 files changed, 6909 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: */ |