| /************************************************************** |
| * |
| * 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 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| #include "precompiled_vcl.hxx" |
| |
| #include "pdfwriter_impl.hxx" |
| |
| #include "vcl/pdfextoutdevdata.hxx" |
| #include "vcl/virdev.hxx" |
| #include "vcl/gdimtf.hxx" |
| #include "vcl/metaact.hxx" |
| #include "vcl/bmpacc.hxx" |
| #include "vcl/graph.hxx" |
| |
| #include "svdata.hxx" |
| |
| #include "unotools/streamwrap.hxx" |
| #include "unotools/processfactory.hxx" |
| |
| #include "comphelper/processfactory.hxx" |
| |
| #include "com/sun/star/beans/PropertyValue.hpp" |
| #include "com/sun/star/io/XSeekable.hpp" |
| #include "com/sun/star/graphic/XGraphicProvider.hpp" |
| |
| #include "cppuhelper/implbase1.hxx" |
| |
| #include <rtl/digest.h> |
| |
| #undef USE_PDFGRADIENTS |
| |
| using namespace vcl; |
| using namespace rtl; |
| using namespace com::sun::star; |
| using namespace com::sun::star::uno; |
| using namespace com::sun::star::beans; |
| |
| // ----------------------------------------------------------------------------- |
| |
| void PDFWriterImpl::implWriteGradient( const PolyPolygon& i_rPolyPoly, const Gradient& i_rGradient, |
| VirtualDevice* i_pDummyVDev, const vcl::PDFWriter::PlayMetafileContext& i_rContext ) |
| { |
| GDIMetaFile aTmpMtf; |
| |
| i_pDummyVDev->AddGradientActions( i_rPolyPoly.GetBoundRect(), i_rGradient, aTmpMtf ); |
| |
| m_rOuterFace.Push(); |
| m_rOuterFace.IntersectClipRegion( i_rPolyPoly.getB2DPolyPolygon() ); |
| playMetafile( aTmpMtf, NULL, i_rContext, i_pDummyVDev ); |
| m_rOuterFace.Pop(); |
| } |
| |
| // ----------------------------------------------------------------------------- |
| |
| void PDFWriterImpl::implWriteBitmapEx( const Point& i_rPoint, const Size& i_rSize, const BitmapEx& i_rBitmapEx, |
| VirtualDevice* i_pDummyVDev, const vcl::PDFWriter::PlayMetafileContext& i_rContext ) |
| { |
| if ( !i_rBitmapEx.IsEmpty() && i_rSize.Width() && i_rSize.Height() ) |
| { |
| BitmapEx aBitmapEx( i_rBitmapEx ); |
| Point aPoint( i_rPoint ); |
| Size aSize( i_rSize ); |
| |
| // #i19065# Negative sizes have mirror semantics on |
| // OutputDevice. BitmapEx and co. have no idea about that, so |
| // perform that _before_ doing anything with aBitmapEx. |
| sal_uLong nMirrorFlags(BMP_MIRROR_NONE); |
| if( aSize.Width() < 0 ) |
| { |
| aSize.Width() *= -1; |
| aPoint.X() -= aSize.Width(); |
| nMirrorFlags |= BMP_MIRROR_HORZ; |
| } |
| if( aSize.Height() < 0 ) |
| { |
| aSize.Height() *= -1; |
| aPoint.Y() -= aSize.Height(); |
| nMirrorFlags |= BMP_MIRROR_VERT; |
| } |
| |
| if( nMirrorFlags != BMP_MIRROR_NONE ) |
| { |
| aBitmapEx.Mirror( nMirrorFlags ); |
| } |
| if( i_rContext.m_nMaxImageResolution > 50 ) |
| { |
| // do downsampling if neccessary |
| const Size aDstSizeTwip( i_pDummyVDev->PixelToLogic( i_pDummyVDev->LogicToPixel( aSize ), MAP_TWIP ) ); |
| const Size aBmpSize( aBitmapEx.GetSizePixel() ); |
| const double fBmpPixelX = aBmpSize.Width(); |
| const double fBmpPixelY = aBmpSize.Height(); |
| const double fMaxPixelX = aDstSizeTwip.Width() * i_rContext.m_nMaxImageResolution / 1440.0; |
| const double fMaxPixelY = aDstSizeTwip.Height() * i_rContext.m_nMaxImageResolution / 1440.0; |
| |
| // check, if the bitmap DPI exceeds the maximum DPI (allow 4 pixel rounding tolerance) |
| if( ( ( fBmpPixelX > ( fMaxPixelX + 4 ) ) || |
| ( fBmpPixelY > ( fMaxPixelY + 4 ) ) ) && |
| ( fBmpPixelY > 0.0 ) && ( fMaxPixelY > 0.0 ) ) |
| { |
| // do scaling |
| Size aNewBmpSize; |
| const double fBmpWH = fBmpPixelX / fBmpPixelY; |
| const double fMaxWH = fMaxPixelX / fMaxPixelY; |
| |
| if( fBmpWH < fMaxWH ) |
| { |
| aNewBmpSize.Width() = FRound( fMaxPixelY * fBmpWH ); |
| aNewBmpSize.Height() = FRound( fMaxPixelY ); |
| } |
| else if( fBmpWH > 0.0 ) |
| { |
| aNewBmpSize.Width() = FRound( fMaxPixelX ); |
| aNewBmpSize.Height() = FRound( fMaxPixelX / fBmpWH); |
| } |
| |
| if( aNewBmpSize.Width() && aNewBmpSize.Height() ) |
| { |
| // #121233# Use best quality for PDF exports |
| aBitmapEx.Scale( aNewBmpSize, BMP_SCALE_BESTQUALITY ); |
| } |
| else |
| { |
| aBitmapEx.SetEmpty(); |
| } |
| } |
| } |
| |
| const Size aSizePixel( aBitmapEx.GetSizePixel() ); |
| if ( aSizePixel.Width() && aSizePixel.Height() ) |
| { |
| if( m_aContext.ColorMode == PDFWriter::DrawGreyscale ) |
| { |
| BmpConversion eConv = BMP_CONVERSION_8BIT_GREYS; |
| int nDepth = aBitmapEx.GetBitmap().GetBitCount(); |
| if( nDepth <= 4 ) |
| eConv = BMP_CONVERSION_4BIT_GREYS; |
| if( nDepth > 1 ) |
| aBitmapEx.Convert( eConv ); |
| } |
| sal_Bool bUseJPGCompression = !i_rContext.m_bOnlyLosslessCompression; |
| if ( ( aSizePixel.Width() < 32 ) || ( aSizePixel.Height() < 32 ) ) |
| bUseJPGCompression = sal_False; |
| |
| SvMemoryStream aStrm; |
| Bitmap aMask; |
| |
| bool bTrueColorJPG = true; |
| if ( bUseJPGCompression ) |
| { |
| sal_uInt32 nZippedFileSize; // sj: we will calculate the filesize of a zipped bitmap |
| { // to determine if jpeg compression is usefull |
| SvMemoryStream aTemp; |
| aTemp.SetCompressMode( aTemp.GetCompressMode() | COMPRESSMODE_ZBITMAP ); |
| aTemp.SetVersion( SOFFICE_FILEFORMAT_40 ); // sj: up from version 40 our bitmap stream operator |
| WriteDIBBitmapEx(aBitmapEx, aTemp); // is capable of zlib stream compression |
| aTemp.Seek( STREAM_SEEK_TO_END ); |
| nZippedFileSize = aTemp.Tell(); |
| } |
| if ( aBitmapEx.IsTransparent() ) |
| { |
| if ( aBitmapEx.IsAlpha() ) |
| aMask = aBitmapEx.GetAlpha().GetBitmap(); |
| else |
| aMask = aBitmapEx.GetMask(); |
| } |
| Graphic aGraphic( aBitmapEx.GetBitmap() ); |
| sal_Int32 nColorMode = 0; |
| |
| Sequence< PropertyValue > aFilterData( 2 ); |
| aFilterData[ 0 ].Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "Quality" ) ); |
| aFilterData[ 0 ].Value <<= sal_Int32(i_rContext.m_nJPEGQuality); |
| aFilterData[ 1 ].Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "ColorMode" ) ); |
| aFilterData[ 1 ].Value <<= nColorMode; |
| |
| try |
| { |
| uno::Reference < io::XStream > xStream = new utl::OStreamWrapper( aStrm ); |
| uno::Reference< io::XSeekable > xSeekable( xStream, UNO_QUERY_THROW ); |
| uno::Reference< graphic::XGraphicProvider > xGraphicProvider( ImplGetSVData()->maAppData.mxMSF->createInstance( |
| OUString::createFromAscii( "com.sun.star.graphic.GraphicProvider" ) ), UNO_QUERY ); |
| if ( xGraphicProvider.is() ) |
| { |
| uno::Reference< graphic::XGraphic > xGraphic( aGraphic.GetXGraphic() ); |
| uno::Reference < io::XOutputStream > xOut( xStream->getOutputStream() ); |
| rtl::OUString aMimeType( ::rtl::OUString::createFromAscii( "image/jpeg" ) ); |
| uno::Sequence< beans::PropertyValue > aOutMediaProperties( 3 ); |
| aOutMediaProperties[0].Name = ::rtl::OUString::createFromAscii( "OutputStream" ); |
| aOutMediaProperties[0].Value <<= xOut; |
| aOutMediaProperties[1].Name = ::rtl::OUString::createFromAscii( "MimeType" ); |
| aOutMediaProperties[1].Value <<= aMimeType; |
| aOutMediaProperties[2].Name = ::rtl::OUString::createFromAscii( "FilterData" ); |
| aOutMediaProperties[2].Value <<= aFilterData; |
| xGraphicProvider->storeGraphic( xGraphic, aOutMediaProperties ); |
| xOut->flush(); |
| if ( xSeekable->getLength() > nZippedFileSize ) |
| { |
| bUseJPGCompression = sal_False; |
| } |
| else |
| { |
| aStrm.Seek( STREAM_SEEK_TO_END ); |
| |
| xSeekable->seek( 0 ); |
| Sequence< PropertyValue > aArgs( 1 ); |
| aArgs[ 0 ].Name = ::rtl::OUString::createFromAscii( "InputStream" ); |
| aArgs[ 0 ].Value <<= xStream; |
| uno::Reference< XPropertySet > xPropSet( xGraphicProvider->queryGraphicDescriptor( aArgs ) ); |
| if ( xPropSet.is() ) |
| { |
| sal_Int16 nBitsPerPixel = 24; |
| if ( xPropSet->getPropertyValue( ::rtl::OUString::createFromAscii( "BitsPerPixel" ) ) >>= nBitsPerPixel ) |
| { |
| bTrueColorJPG = nBitsPerPixel != 8; |
| } |
| } |
| } |
| } |
| else |
| bUseJPGCompression = sal_False; |
| } |
| catch( uno::Exception& ) |
| { |
| bUseJPGCompression = sal_False; |
| } |
| } |
| if ( bUseJPGCompression ) |
| m_rOuterFace.DrawJPGBitmap( aStrm, bTrueColorJPG, aSizePixel, Rectangle( aPoint, aSize ), aMask ); |
| else if ( aBitmapEx.IsTransparent() ) |
| m_rOuterFace.DrawBitmapEx( aPoint, aSize, aBitmapEx ); |
| else |
| m_rOuterFace.DrawBitmap( aPoint, aSize, aBitmapEx.GetBitmap() ); |
| } |
| } |
| } |
| |
| |
| // ----------------------------------------------------------------------------- |
| |
| void PDFWriterImpl::playMetafile( const GDIMetaFile& i_rMtf, vcl::PDFExtOutDevData* i_pOutDevData, const vcl::PDFWriter::PlayMetafileContext& i_rContext, VirtualDevice* pDummyVDev ) |
| { |
| bool bAssertionFired( false ); |
| |
| VirtualDevice* pPrivateDevice = NULL; |
| if( ! pDummyVDev ) |
| { |
| pPrivateDevice = pDummyVDev = new VirtualDevice(); |
| pDummyVDev->EnableOutput( sal_False ); |
| pDummyVDev->SetMapMode( i_rMtf.GetPrefMapMode() ); |
| } |
| GDIMetaFile aMtf( i_rMtf ); |
| |
| for( sal_uInt32 i = 0, nCount = aMtf.GetActionCount(); i < nCount; ) |
| { |
| if ( !i_pOutDevData || !i_pOutDevData->PlaySyncPageAct( m_rOuterFace, i ) ) |
| { |
| const MetaAction* pAction = aMtf.GetAction( i ); |
| const sal_uInt16 nType = pAction->GetType(); |
| |
| switch( nType ) |
| { |
| case( META_PIXEL_ACTION ): |
| { |
| const MetaPixelAction* pA = (const MetaPixelAction*) pAction; |
| m_rOuterFace.DrawPixel( pA->GetPoint(), pA->GetColor() ); |
| } |
| break; |
| |
| case( META_POINT_ACTION ): |
| { |
| const MetaPointAction* pA = (const MetaPointAction*) pAction; |
| m_rOuterFace.DrawPixel( pA->GetPoint() ); |
| } |
| break; |
| |
| case( META_LINE_ACTION ): |
| { |
| const MetaLineAction* pA = (const MetaLineAction*) pAction; |
| if ( pA->GetLineInfo().IsDefault() ) |
| m_rOuterFace.DrawLine( pA->GetStartPoint(), pA->GetEndPoint() ); |
| else |
| m_rOuterFace.DrawLine( pA->GetStartPoint(), pA->GetEndPoint(), pA->GetLineInfo() ); |
| } |
| break; |
| |
| case( META_RECT_ACTION ): |
| { |
| const MetaRectAction* pA = (const MetaRectAction*) pAction; |
| m_rOuterFace.DrawRect( pA->GetRect() ); |
| } |
| break; |
| |
| case( META_ROUNDRECT_ACTION ): |
| { |
| const MetaRoundRectAction* pA = (const MetaRoundRectAction*) pAction; |
| m_rOuterFace.DrawRect( pA->GetRect(), pA->GetHorzRound(), pA->GetVertRound() ); |
| } |
| break; |
| |
| case( META_ELLIPSE_ACTION ): |
| { |
| const MetaEllipseAction* pA = (const MetaEllipseAction*) pAction; |
| m_rOuterFace.DrawEllipse( pA->GetRect() ); |
| } |
| break; |
| |
| case( META_ARC_ACTION ): |
| { |
| const MetaArcAction* pA = (const MetaArcAction*) pAction; |
| m_rOuterFace.DrawArc( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() ); |
| } |
| break; |
| |
| case( META_PIE_ACTION ): |
| { |
| const MetaArcAction* pA = (const MetaArcAction*) pAction; |
| m_rOuterFace.DrawPie( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() ); |
| } |
| break; |
| |
| case( META_CHORD_ACTION ): |
| { |
| const MetaChordAction* pA = (const MetaChordAction*) pAction; |
| m_rOuterFace.DrawChord( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() ); |
| } |
| break; |
| |
| case( META_POLYGON_ACTION ): |
| { |
| const MetaPolygonAction* pA = (const MetaPolygonAction*) pAction; |
| m_rOuterFace.DrawPolygon( pA->GetPolygon() ); |
| } |
| break; |
| |
| case( META_POLYLINE_ACTION ): |
| { |
| const MetaPolyLineAction* pA = (const MetaPolyLineAction*) pAction; |
| if ( pA->GetLineInfo().IsDefault() ) |
| m_rOuterFace.DrawPolyLine( pA->GetPolygon() ); |
| else |
| m_rOuterFace.DrawPolyLine( pA->GetPolygon(), pA->GetLineInfo() ); |
| } |
| break; |
| |
| case( META_POLYPOLYGON_ACTION ): |
| { |
| const MetaPolyPolygonAction* pA = (const MetaPolyPolygonAction*) pAction; |
| m_rOuterFace.DrawPolyPolygon( pA->GetPolyPolygon() ); |
| } |
| break; |
| |
| case( META_GRADIENT_ACTION ): |
| { |
| const MetaGradientAction* pA = (const MetaGradientAction*) pAction; |
| #ifdef USE_PDFGRADIENTS |
| m_rOuterFace.DrawGradient( pA->GetRect(), pA->GetGradient() ); |
| #else |
| const PolyPolygon aPolyPoly( pA->GetRect() ); |
| implWriteGradient( aPolyPoly, pA->GetGradient(), pDummyVDev, i_rContext ); |
| #endif |
| } |
| break; |
| |
| case( META_GRADIENTEX_ACTION ): |
| { |
| const MetaGradientExAction* pA = (const MetaGradientExAction*) pAction; |
| #ifdef USE_PDFGRADIENTS |
| m_rOuterFace.DrawGradient( pA->GetPolyPolygon(), pA->GetGradient() ); |
| #else |
| implWriteGradient( pA->GetPolyPolygon(), pA->GetGradient(), pDummyVDev, i_rContext ); |
| #endif |
| } |
| break; |
| |
| case META_HATCH_ACTION: |
| { |
| const MetaHatchAction* pA = (const MetaHatchAction*) pAction; |
| m_rOuterFace.DrawHatch( pA->GetPolyPolygon(), pA->GetHatch() ); |
| } |
| break; |
| |
| case( META_TRANSPARENT_ACTION ): |
| { |
| const MetaTransparentAction* pA = (const MetaTransparentAction*) pAction; |
| m_rOuterFace.DrawTransparent( pA->GetPolyPolygon(), pA->GetTransparence() ); |
| } |
| break; |
| |
| case( META_FLOATTRANSPARENT_ACTION ): |
| { |
| const MetaFloatTransparentAction* pA = (const MetaFloatTransparentAction*) pAction; |
| |
| GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() ); |
| const Point& rPos = pA->GetPoint(); |
| const Size& rSize= pA->GetSize(); |
| const Gradient& rTransparenceGradient = pA->GetGradient(); |
| |
| // special case constant alpha value |
| if( rTransparenceGradient.GetStartColor() == rTransparenceGradient.GetEndColor() ) |
| { |
| const Color aTransCol( rTransparenceGradient.GetStartColor() ); |
| const sal_uInt16 nTransPercent = aTransCol.GetLuminance() * 100 / 255; |
| m_rOuterFace.BeginTransparencyGroup(); |
| playMetafile( aTmpMtf, NULL, i_rContext, pDummyVDev ); |
| m_rOuterFace.EndTransparencyGroup( Rectangle( rPos, rSize ), nTransPercent ); |
| } |
| else |
| { |
| const Size aDstSizeTwip( pDummyVDev->PixelToLogic( pDummyVDev->LogicToPixel( rSize ), MAP_TWIP ) ); |
| |
| // #115962# Always use at least 300 DPI for bitmap conversion of transparence gradients, |
| // else the quality is not acceptable (see bugdoc as example) |
| // sal_Int32 nMaxBmpDPI = i_rContext.m_bOnlyLosslessCompression ? 300 : 72; |
| sal_Int32 nMaxBmpDPI(300); |
| |
| if( i_rContext.m_nMaxImageResolution > 50 ) |
| { |
| if ( nMaxBmpDPI > i_rContext.m_nMaxImageResolution ) |
| nMaxBmpDPI = i_rContext.m_nMaxImageResolution; |
| } |
| const sal_Int32 nPixelX = (sal_Int32)((double)aDstSizeTwip.Width() * (double)nMaxBmpDPI / 1440.0); |
| const sal_Int32 nPixelY = (sal_Int32)((double)aDstSizeTwip.Height() * (double)nMaxBmpDPI / 1440.0); |
| if ( nPixelX && nPixelY ) |
| { |
| Size aDstSizePixel( nPixelX, nPixelY ); |
| VirtualDevice* pVDev = new VirtualDevice; |
| if( pVDev->SetOutputSizePixel( aDstSizePixel ) ) |
| { |
| Bitmap aPaint, aMask; |
| AlphaMask aAlpha; |
| Point aPoint; |
| |
| MapMode aMapMode( pDummyVDev->GetMapMode() ); |
| aMapMode.SetOrigin( aPoint ); |
| pVDev->SetMapMode( aMapMode ); |
| Size aDstSize( pVDev->PixelToLogic( aDstSizePixel ) ); |
| |
| Point aMtfOrigin( aTmpMtf.GetPrefMapMode().GetOrigin() ); |
| if ( aMtfOrigin.X() || aMtfOrigin.Y() ) |
| aTmpMtf.Move( -aMtfOrigin.X(), -aMtfOrigin.Y() ); |
| double fScaleX = (double)aDstSize.Width() / (double)aTmpMtf.GetPrefSize().Width(); |
| double fScaleY = (double)aDstSize.Height() / (double)aTmpMtf.GetPrefSize().Height(); |
| if( fScaleX != 1.0 || fScaleY != 1.0 ) |
| aTmpMtf.Scale( fScaleX, fScaleY ); |
| aTmpMtf.SetPrefMapMode( aMapMode ); |
| |
| // create paint bitmap |
| aTmpMtf.WindStart(); |
| aTmpMtf.Play( pVDev, aPoint, aDstSize ); |
| aTmpMtf.WindStart(); |
| |
| pVDev->EnableMapMode( sal_False ); |
| aPaint = pVDev->GetBitmap( aPoint, aDstSizePixel ); |
| pVDev->EnableMapMode( sal_True ); |
| |
| // create mask bitmap |
| pVDev->SetLineColor( COL_BLACK ); |
| pVDev->SetFillColor( COL_BLACK ); |
| pVDev->DrawRect( Rectangle( aPoint, aDstSize ) ); |
| pVDev->SetDrawMode( DRAWMODE_WHITELINE | DRAWMODE_WHITEFILL | DRAWMODE_WHITETEXT | |
| DRAWMODE_WHITEBITMAP | DRAWMODE_WHITEGRADIENT ); |
| aTmpMtf.WindStart(); |
| aTmpMtf.Play( pVDev, aPoint, aDstSize ); |
| aTmpMtf.WindStart(); |
| pVDev->EnableMapMode( sal_False ); |
| aMask = pVDev->GetBitmap( aPoint, aDstSizePixel ); |
| pVDev->EnableMapMode( sal_True ); |
| |
| // create alpha mask from gradient |
| pVDev->SetDrawMode( DRAWMODE_GRAYGRADIENT ); |
| pVDev->DrawGradient( Rectangle( aPoint, aDstSize ), rTransparenceGradient ); |
| pVDev->SetDrawMode( DRAWMODE_DEFAULT ); |
| pVDev->EnableMapMode( sal_False ); |
| pVDev->DrawMask( aPoint, aDstSizePixel, aMask, Color( COL_WHITE ) ); |
| aAlpha = pVDev->GetBitmap( aPoint, aDstSizePixel ); |
| implWriteBitmapEx( rPos, rSize, BitmapEx( aPaint, aAlpha ), pDummyVDev, i_rContext ); |
| } |
| delete pVDev; |
| } |
| } |
| } |
| break; |
| |
| case( META_EPS_ACTION ): |
| { |
| const MetaEPSAction* pA = (const MetaEPSAction*) pAction; |
| const GDIMetaFile aSubstitute( pA->GetSubstitute() ); |
| |
| m_rOuterFace.Push(); |
| pDummyVDev->Push(); |
| |
| MapMode aMapMode( aSubstitute.GetPrefMapMode() ); |
| Size aOutSize( pDummyVDev->LogicToLogic( pA->GetSize(), pDummyVDev->GetMapMode(), aMapMode ) ); |
| aMapMode.SetScaleX( Fraction( aOutSize.Width(), aSubstitute.GetPrefSize().Width() ) ); |
| aMapMode.SetScaleY( Fraction( aOutSize.Height(), aSubstitute.GetPrefSize().Height() ) ); |
| aMapMode.SetOrigin( pDummyVDev->LogicToLogic( pA->GetPoint(), pDummyVDev->GetMapMode(), aMapMode ) ); |
| |
| m_rOuterFace.SetMapMode( aMapMode ); |
| pDummyVDev->SetMapMode( aMapMode ); |
| playMetafile( aSubstitute, NULL, i_rContext, pDummyVDev ); |
| pDummyVDev->Pop(); |
| m_rOuterFace.Pop(); |
| } |
| break; |
| |
| case( META_COMMENT_ACTION ): |
| if( ! i_rContext.m_bTransparenciesWereRemoved ) |
| { |
| const MetaCommentAction* pA = (const MetaCommentAction*) pAction; |
| String aSkipComment; |
| |
| if( pA->GetComment().CompareIgnoreCaseToAscii( "XGRAD_SEQ_BEGIN" ) == COMPARE_EQUAL ) |
| { |
| const MetaGradientExAction* pGradAction = NULL; |
| sal_Bool bDone = sal_False; |
| |
| while( !bDone && ( ++i < nCount ) ) |
| { |
| pAction = aMtf.GetAction( i ); |
| |
| if( pAction->GetType() == META_GRADIENTEX_ACTION ) |
| pGradAction = (const MetaGradientExAction*) pAction; |
| else if( ( pAction->GetType() == META_COMMENT_ACTION ) && |
| ( ( (const MetaCommentAction*) pAction )->GetComment().CompareIgnoreCaseToAscii( "XGRAD_SEQ_END" ) == COMPARE_EQUAL ) ) |
| { |
| bDone = sal_True; |
| } |
| } |
| |
| if( pGradAction ) |
| { |
| #if USE_PDFGRADIENTS |
| m_rOuterFace.DrawGradient( pGradAction->GetPolyPolygon(), pGradAction->GetGradient() ); |
| #else |
| implWriteGradient( pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), pDummyVDev, i_rContext ); |
| #endif |
| } |
| } |
| else |
| { |
| const sal_uInt8* pData = pA->GetData(); |
| if ( pData ) |
| { |
| SvMemoryStream aMemStm( (void*)pData, pA->GetDataSize(), STREAM_READ ); |
| sal_Bool bSkipSequence = sal_False; |
| ByteString sSeqEnd; |
| |
| if( pA->GetComment().Equals( "XPATHSTROKE_SEQ_BEGIN" ) ) |
| { |
| sSeqEnd = ByteString( "XPATHSTROKE_SEQ_END" ); |
| SvtGraphicStroke aStroke; |
| aMemStm >> aStroke; |
| |
| Polygon aPath; |
| aStroke.getPath( aPath ); |
| |
| PolyPolygon aStartArrow; |
| PolyPolygon aEndArrow; |
| double fTransparency( aStroke.getTransparency() ); |
| double fStrokeWidth( aStroke.getStrokeWidth() ); |
| SvtGraphicStroke::DashArray aDashArray; |
| |
| aStroke.getStartArrow( aStartArrow ); |
| aStroke.getEndArrow( aEndArrow ); |
| aStroke.getDashArray( aDashArray ); |
| |
| bSkipSequence = sal_True; |
| if ( aStartArrow.Count() || aEndArrow.Count() ) |
| bSkipSequence = sal_False; |
| if ( aDashArray.size() && ( fStrokeWidth != 0.0 ) && ( fTransparency == 0.0 ) ) |
| bSkipSequence = sal_False; |
| if ( bSkipSequence ) |
| { |
| PDFWriter::ExtLineInfo aInfo; |
| aInfo.m_fLineWidth = fStrokeWidth; |
| aInfo.m_fTransparency = fTransparency; |
| aInfo.m_fMiterLimit = aStroke.getMiterLimit(); |
| switch( aStroke.getCapType() ) |
| { |
| default: |
| case SvtGraphicStroke::capButt: aInfo.m_eCap = PDFWriter::capButt;break; |
| case SvtGraphicStroke::capRound: aInfo.m_eCap = PDFWriter::capRound;break; |
| case SvtGraphicStroke::capSquare: aInfo.m_eCap = PDFWriter::capSquare;break; |
| } |
| switch( aStroke.getJoinType() ) |
| { |
| default: |
| case SvtGraphicStroke::joinMiter: aInfo.m_eJoin = PDFWriter::joinMiter;break; |
| case SvtGraphicStroke::joinRound: aInfo.m_eJoin = PDFWriter::joinRound;break; |
| case SvtGraphicStroke::joinBevel: aInfo.m_eJoin = PDFWriter::joinBevel;break; |
| case SvtGraphicStroke::joinNone: |
| aInfo.m_eJoin = PDFWriter::joinMiter; |
| aInfo.m_fMiterLimit = 0.0; |
| break; |
| } |
| aInfo.m_aDashArray = aDashArray; |
| |
| if(SvtGraphicStroke::joinNone == aStroke.getJoinType() |
| && fStrokeWidth > 0.0) |
| { |
| // emulate no edge rounding by handling single edges |
| const sal_uInt16 nPoints(aPath.GetSize()); |
| const bool bCurve(aPath.HasFlags()); |
| |
| for(sal_uInt16 a(0); a + 1 < nPoints; a++) |
| { |
| if(bCurve |
| && POLY_NORMAL != aPath.GetFlags(a + 1) |
| && a + 2 < nPoints |
| && POLY_NORMAL != aPath.GetFlags(a + 2) |
| && a + 3 < nPoints) |
| { |
| const Polygon aSnippet(4, |
| aPath.GetConstPointAry() + a, |
| aPath.GetConstFlagAry() + a); |
| m_rOuterFace.DrawPolyLine( aSnippet, aInfo ); |
| a += 2; |
| } |
| else |
| { |
| const Polygon aSnippet(2, |
| aPath.GetConstPointAry() + a); |
| m_rOuterFace.DrawPolyLine( aSnippet, aInfo ); |
| } |
| } |
| } |
| else |
| { |
| m_rOuterFace.DrawPolyLine( aPath, aInfo ); |
| } |
| } |
| } |
| else if ( pA->GetComment().Equals( "XPATHFILL_SEQ_BEGIN" ) ) |
| { |
| sSeqEnd = ByteString( "XPATHFILL_SEQ_END" ); |
| SvtGraphicFill aFill; |
| aMemStm >> aFill; |
| |
| if ( ( aFill.getFillType() == SvtGraphicFill::fillSolid ) && ( aFill.getFillRule() == SvtGraphicFill::fillEvenOdd ) ) |
| { |
| double fTransparency = aFill.getTransparency(); |
| if ( fTransparency == 0.0 ) |
| { |
| PolyPolygon aPath; |
| aFill.getPath( aPath ); |
| |
| bSkipSequence = sal_True; |
| m_rOuterFace.DrawPolyPolygon( aPath ); |
| } |
| else if ( fTransparency == 1.0 ) |
| bSkipSequence = sal_True; |
| } |
| /* #i81548# removing optimization for fill textures, because most of the texture settings are not |
| exported properly. In OpenOffice 3.1 the drawing layer will support graphic primitives, then it |
| will not be a problem to optimize the filltexture export. But for wysiwyg is more important than |
| filesize. |
| else if( aFill.getFillType() == SvtGraphicFill::fillTexture && aFill.isTiling() ) |
| { |
| sal_Int32 nPattern = mnCachePatternId; |
| Graphic aPatternGraphic; |
| aFill.getGraphic( aPatternGraphic ); |
| bool bUseCache = false; |
| SvtGraphicFill::Transform aPatTransform; |
| aFill.getTransform( aPatTransform ); |
| |
| if( mnCachePatternId >= 0 ) |
| { |
| SvtGraphicFill::Transform aCacheTransform; |
| maCacheFill.getTransform( aCacheTransform ); |
| if( aCacheTransform.matrix[0] == aPatTransform.matrix[0] && |
| aCacheTransform.matrix[1] == aPatTransform.matrix[1] && |
| aCacheTransform.matrix[2] == aPatTransform.matrix[2] && |
| aCacheTransform.matrix[3] == aPatTransform.matrix[3] && |
| aCacheTransform.matrix[4] == aPatTransform.matrix[4] && |
| aCacheTransform.matrix[5] == aPatTransform.matrix[5] |
| ) |
| { |
| Graphic aCacheGraphic; |
| maCacheFill.getGraphic( aCacheGraphic ); |
| if( aCacheGraphic == aPatternGraphic ) |
| bUseCache = true; |
| } |
| } |
| |
| if( ! bUseCache ) |
| { |
| |
| // paint graphic to metafile |
| GDIMetaFile aPattern; |
| pDummyVDev->SetConnectMetaFile( &aPattern ); |
| pDummyVDev->Push(); |
| pDummyVDev->SetMapMode( aPatternGraphic.GetPrefMapMode() ); |
| |
| aPatternGraphic.Draw( &rDummyVDev, Point( 0, 0 ) ); |
| pDummyVDev->Pop(); |
| pDummyVDev->SetConnectMetaFile( NULL ); |
| aPattern.WindStart(); |
| |
| MapMode aPatternMapMode( aPatternGraphic.GetPrefMapMode() ); |
| // prepare pattern from metafile |
| Size aPrefSize( aPatternGraphic.GetPrefSize() ); |
| // FIXME: this magic -1 shouldn't be necessary |
| aPrefSize.Width() -= 1; |
| aPrefSize.Height() -= 1; |
| aPrefSize = m_rOuterFace.GetReferenceDevice()-> |
| LogicToLogic( aPrefSize, |
| &aPatternMapMode, |
| &m_rOuterFace.GetReferenceDevice()->GetMapMode() ); |
| // build bounding rectangle of pattern |
| Rectangle aBound( Point( 0, 0 ), aPrefSize ); |
| m_rOuterFace.BeginPattern( aBound ); |
| m_rOuterFace.Push(); |
| pDummyVDev->Push(); |
| m_rOuterFace.SetMapMode( aPatternMapMode ); |
| pDummyVDev->SetMapMode( aPatternMapMode ); |
| ImplWriteActions( m_rOuterFace, NULL, aPattern, rDummyVDev ); |
| pDummyVDev->Pop(); |
| m_rOuterFace.Pop(); |
| |
| nPattern = m_rOuterFace.EndPattern( aPatTransform ); |
| |
| // try some caching and reuse pattern |
| mnCachePatternId = nPattern; |
| maCacheFill = aFill; |
| } |
| |
| // draw polypolygon with pattern fill |
| PolyPolygon aPath; |
| aFill.getPath( aPath ); |
| m_rOuterFace.DrawPolyPolygon( aPath, nPattern, aFill.getFillRule() == SvtGraphicFill::fillEvenOdd ); |
| |
| bSkipSequence = sal_True; |
| } |
| */ |
| } |
| if ( bSkipSequence ) |
| { |
| while( ++i < nCount ) |
| { |
| pAction = aMtf.GetAction( i ); |
| if ( pAction->GetType() == META_COMMENT_ACTION ) |
| { |
| ByteString sComment( ((MetaCommentAction*)pAction)->GetComment() ); |
| if ( sComment.Equals( sSeqEnd ) ) |
| break; |
| } |
| // #i44496# |
| // the replacement action for stroke is a filled rectangle |
| // the set fillcolor of the replacement is part of the graphics |
| // state and must not be skipped |
| else if( pAction->GetType() == META_FILLCOLOR_ACTION ) |
| { |
| const MetaFillColorAction* pMA = (const MetaFillColorAction*) pAction; |
| if( pMA->IsSetting() ) |
| m_rOuterFace.SetFillColor( pMA->GetColor() ); |
| else |
| m_rOuterFace.SetFillColor(); |
| } |
| } |
| } |
| } |
| } |
| } |
| break; |
| |
| case( META_BMP_ACTION ): |
| { |
| const MetaBmpAction* pA = (const MetaBmpAction*) pAction; |
| BitmapEx aBitmapEx( pA->GetBitmap() ); |
| Size aSize( OutputDevice::LogicToLogic( aBitmapEx.GetPrefSize(), |
| aBitmapEx.GetPrefMapMode(), pDummyVDev->GetMapMode() ) ); |
| if( ! ( aSize.Width() && aSize.Height() ) ) |
| aSize = pDummyVDev->PixelToLogic( aBitmapEx.GetSizePixel() ); |
| implWriteBitmapEx( pA->GetPoint(), aSize, aBitmapEx, pDummyVDev, i_rContext ); |
| } |
| break; |
| |
| case( META_BMPSCALE_ACTION ): |
| { |
| const MetaBmpScaleAction* pA = (const MetaBmpScaleAction*) pAction; |
| implWriteBitmapEx( pA->GetPoint(), pA->GetSize(), BitmapEx( pA->GetBitmap() ), pDummyVDev, i_rContext ); |
| } |
| break; |
| |
| case( META_BMPSCALEPART_ACTION ): |
| { |
| const MetaBmpScalePartAction* pA = (const MetaBmpScalePartAction*) pAction; |
| BitmapEx aBitmapEx( pA->GetBitmap() ); |
| aBitmapEx.Crop( Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) ); |
| implWriteBitmapEx( pA->GetDestPoint(), pA->GetDestSize(), aBitmapEx, pDummyVDev, i_rContext ); |
| } |
| break; |
| |
| case( META_BMPEX_ACTION ): |
| { |
| const MetaBmpExAction* pA = (const MetaBmpExAction*) pAction; |
| BitmapEx aBitmapEx( pA->GetBitmapEx() ); |
| Size aSize( OutputDevice::LogicToLogic( aBitmapEx.GetPrefSize(), |
| aBitmapEx.GetPrefMapMode(), pDummyVDev->GetMapMode() ) ); |
| implWriteBitmapEx( pA->GetPoint(), aSize, aBitmapEx, pDummyVDev, i_rContext ); |
| } |
| break; |
| |
| case( META_BMPEXSCALE_ACTION ): |
| { |
| const MetaBmpExScaleAction* pA = (const MetaBmpExScaleAction*) pAction; |
| implWriteBitmapEx( pA->GetPoint(), pA->GetSize(), pA->GetBitmapEx(), pDummyVDev, i_rContext ); |
| } |
| break; |
| |
| case( META_BMPEXSCALEPART_ACTION ): |
| { |
| const MetaBmpExScalePartAction* pA = (const MetaBmpExScalePartAction*) pAction; |
| BitmapEx aBitmapEx( pA->GetBitmapEx() ); |
| aBitmapEx.Crop( Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) ); |
| implWriteBitmapEx( pA->GetDestPoint(), pA->GetDestSize(), aBitmapEx, pDummyVDev, i_rContext ); |
| } |
| break; |
| |
| case( META_MASK_ACTION ): |
| case( META_MASKSCALE_ACTION ): |
| case( META_MASKSCALEPART_ACTION ): |
| { |
| DBG_ERROR( "MetaMask...Action not supported yet" ); |
| } |
| break; |
| |
| case( META_TEXT_ACTION ): |
| { |
| const MetaTextAction* pA = (const MetaTextAction*) pAction; |
| m_rOuterFace.DrawText( pA->GetPoint(), String( pA->GetText(), pA->GetIndex(), pA->GetLen() ) ); |
| } |
| break; |
| |
| case( META_TEXTRECT_ACTION ): |
| { |
| const MetaTextRectAction* pA = (const MetaTextRectAction*) pAction; |
| m_rOuterFace.DrawText( pA->GetRect(), String( pA->GetText() ), pA->GetStyle() ); |
| } |
| break; |
| |
| case( META_TEXTARRAY_ACTION ): |
| { |
| const MetaTextArrayAction* pA = (const MetaTextArrayAction*) pAction; |
| m_rOuterFace.DrawTextArray( pA->GetPoint(), pA->GetText(), pA->GetDXArray(), pA->GetIndex(), pA->GetLen() ); |
| } |
| break; |
| |
| case( META_STRETCHTEXT_ACTION ): |
| { |
| const MetaStretchTextAction* pA = (const MetaStretchTextAction*) pAction; |
| m_rOuterFace.DrawStretchText( pA->GetPoint(), pA->GetWidth(), pA->GetText(), pA->GetIndex(), pA->GetLen() ); |
| } |
| break; |
| |
| |
| case( META_TEXTLINE_ACTION ): |
| { |
| const MetaTextLineAction* pA = (const MetaTextLineAction*) pAction; |
| m_rOuterFace.DrawTextLine( pA->GetStartPoint(), pA->GetWidth(), pA->GetStrikeout(), pA->GetUnderline(), pA->GetOverline() ); |
| |
| } |
| break; |
| |
| case( META_CLIPREGION_ACTION ): |
| { |
| const MetaClipRegionAction* pA = (const MetaClipRegionAction*) pAction; |
| |
| if( pA->IsClipping() ) |
| { |
| if( pA->GetRegion().IsEmpty() ) |
| m_rOuterFace.SetClipRegion( basegfx::B2DPolyPolygon() ); |
| else |
| { |
| Region aReg( pA->GetRegion() ); |
| m_rOuterFace.SetClipRegion( aReg.GetAsB2DPolyPolygon() ); |
| } |
| } |
| else |
| m_rOuterFace.SetClipRegion(); |
| } |
| break; |
| |
| case( META_ISECTRECTCLIPREGION_ACTION ): |
| { |
| const MetaISectRectClipRegionAction* pA = (const MetaISectRectClipRegionAction*) pAction; |
| m_rOuterFace.IntersectClipRegion( pA->GetRect() ); |
| } |
| break; |
| |
| case( META_ISECTREGIONCLIPREGION_ACTION ): |
| { |
| const MetaISectRegionClipRegionAction* pA = (const MetaISectRegionClipRegionAction*) pAction; |
| Region aReg( pA->GetRegion() ); |
| m_rOuterFace.IntersectClipRegion( aReg.GetAsB2DPolyPolygon() ); |
| } |
| break; |
| |
| case( META_MOVECLIPREGION_ACTION ): |
| { |
| const MetaMoveClipRegionAction* pA = (const MetaMoveClipRegionAction*) pAction; |
| m_rOuterFace.MoveClipRegion( pA->GetHorzMove(), pA->GetVertMove() ); |
| } |
| break; |
| |
| case( META_MAPMODE_ACTION ): |
| { |
| const_cast< MetaAction* >( pAction )->Execute( pDummyVDev ); |
| m_rOuterFace.SetMapMode( pDummyVDev->GetMapMode() ); |
| } |
| break; |
| |
| case( META_LINECOLOR_ACTION ): |
| { |
| const MetaLineColorAction* pA = (const MetaLineColorAction*) pAction; |
| |
| if( pA->IsSetting() ) |
| m_rOuterFace.SetLineColor( pA->GetColor() ); |
| else |
| m_rOuterFace.SetLineColor(); |
| } |
| break; |
| |
| case( META_FILLCOLOR_ACTION ): |
| { |
| const MetaFillColorAction* pA = (const MetaFillColorAction*) pAction; |
| |
| if( pA->IsSetting() ) |
| m_rOuterFace.SetFillColor( pA->GetColor() ); |
| else |
| m_rOuterFace.SetFillColor(); |
| } |
| break; |
| |
| case( META_TEXTLINECOLOR_ACTION ): |
| { |
| const MetaTextLineColorAction* pA = (const MetaTextLineColorAction*) pAction; |
| |
| if( pA->IsSetting() ) |
| m_rOuterFace.SetTextLineColor( pA->GetColor() ); |
| else |
| m_rOuterFace.SetTextLineColor(); |
| } |
| break; |
| |
| case( META_OVERLINECOLOR_ACTION ): |
| { |
| const MetaOverlineColorAction* pA = (const MetaOverlineColorAction*) pAction; |
| |
| if( pA->IsSetting() ) |
| m_rOuterFace.SetOverlineColor( pA->GetColor() ); |
| else |
| m_rOuterFace.SetOverlineColor(); |
| } |
| break; |
| |
| case( META_TEXTFILLCOLOR_ACTION ): |
| { |
| const MetaTextFillColorAction* pA = (const MetaTextFillColorAction*) pAction; |
| |
| if( pA->IsSetting() ) |
| m_rOuterFace.SetTextFillColor( pA->GetColor() ); |
| else |
| m_rOuterFace.SetTextFillColor(); |
| } |
| break; |
| |
| case( META_TEXTCOLOR_ACTION ): |
| { |
| const MetaTextColorAction* pA = (const MetaTextColorAction*) pAction; |
| m_rOuterFace.SetTextColor( pA->GetColor() ); |
| } |
| break; |
| |
| case( META_TEXTALIGN_ACTION ): |
| { |
| const MetaTextAlignAction* pA = (const MetaTextAlignAction*) pAction; |
| m_rOuterFace.SetTextAlign( pA->GetTextAlign() ); |
| } |
| break; |
| |
| case( META_FONT_ACTION ): |
| { |
| const MetaFontAction* pA = (const MetaFontAction*) pAction; |
| m_rOuterFace.SetFont( pA->GetFont() ); |
| } |
| break; |
| |
| case( META_PUSH_ACTION ): |
| { |
| const MetaPushAction* pA = (const MetaPushAction*) pAction; |
| |
| pDummyVDev->Push( pA->GetFlags() ); |
| m_rOuterFace.Push( pA->GetFlags() ); |
| } |
| break; |
| |
| case( META_POP_ACTION ): |
| { |
| pDummyVDev->Pop(); |
| m_rOuterFace.Pop(); |
| } |
| break; |
| |
| case( META_LAYOUTMODE_ACTION ): |
| { |
| const MetaLayoutModeAction* pA = (const MetaLayoutModeAction*) pAction; |
| m_rOuterFace.SetLayoutMode( pA->GetLayoutMode() ); |
| } |
| break; |
| |
| case META_TEXTLANGUAGE_ACTION: |
| { |
| const MetaTextLanguageAction* pA = (const MetaTextLanguageAction*) pAction; |
| m_rOuterFace.SetDigitLanguage( pA->GetTextLanguage() ); |
| } |
| break; |
| |
| case( META_WALLPAPER_ACTION ): |
| { |
| const MetaWallpaperAction* pA = (const MetaWallpaperAction*) pAction; |
| m_rOuterFace.DrawWallpaper( pA->GetRect(), pA->GetWallpaper() ); |
| } |
| break; |
| |
| case( META_RASTEROP_ACTION ): |
| { |
| // !!! >>> we don't want to support this actions |
| } |
| break; |
| |
| case( META_REFPOINT_ACTION ): |
| { |
| // !!! >>> we don't want to support this actions |
| } |
| break; |
| |
| default: |
| // #i24604# Made assertion fire only once per |
| // metafile. The asserted actions here are all |
| // deprecated |
| if( !bAssertionFired ) |
| { |
| bAssertionFired = true; |
| DBG_ERROR( "PDFExport::ImplWriteActions: deprecated and unsupported MetaAction encountered" ); |
| } |
| break; |
| } |
| i++; |
| } |
| } |
| |
| delete pPrivateDevice; |
| } |
| |
| // Encryption methods |
| |
| /* a crutch to transport an rtlDigest safely though UNO API |
| this is needed for the PDF export dialog, which otherwise would have to pass |
| clear text passwords down till they can be used in PDFWriter. Unfortunately |
| the MD5 sum of the password (which is needed to create the PDF encryption key) |
| is not sufficient, since an rtl MD5 digest cannot be created in an arbitrary state |
| which would be needed in PDFWriterImpl::computeEncryptionKey. |
| */ |
| class EncHashTransporter : public cppu::WeakImplHelper1 < com::sun::star::beans::XMaterialHolder > |
| { |
| rtlDigest maUDigest; |
| sal_IntPtr maID; |
| std::vector< sal_uInt8 > maOValue; |
| |
| static std::map< sal_IntPtr, EncHashTransporter* > sTransporters; |
| public: |
| EncHashTransporter() |
| : maUDigest( rtl_digest_createMD5() ) |
| { |
| maID = reinterpret_cast< sal_IntPtr >(this); |
| while( sTransporters.find( maID ) != sTransporters.end() ) // paranoia mode |
| maID++; |
| sTransporters[ maID ] = this; |
| } |
| |
| virtual ~EncHashTransporter() |
| { |
| sTransporters.erase( maID ); |
| if( maUDigest ) |
| rtl_digest_destroyMD5( maUDigest ); |
| OSL_TRACE( "EncHashTransporter freed\n" ); |
| } |
| |
| rtlDigest getUDigest() const { return maUDigest; }; |
| std::vector< sal_uInt8 >& getOValue() { return maOValue; } |
| void invalidate() |
| { |
| if( maUDigest ) |
| { |
| rtl_digest_destroyMD5( maUDigest ); |
| maUDigest = NULL; |
| } |
| } |
| |
| // XMaterialHolder |
| virtual uno::Any SAL_CALL getMaterial() throw() |
| { |
| return uno::makeAny( sal_Int64(maID) ); |
| } |
| |
| static EncHashTransporter* getEncHashTransporter( const uno::Reference< beans::XMaterialHolder >& ); |
| |
| }; |
| |
| std::map< sal_IntPtr, EncHashTransporter* > EncHashTransporter::sTransporters; |
| |
| EncHashTransporter* EncHashTransporter::getEncHashTransporter( const uno::Reference< beans::XMaterialHolder >& xRef ) |
| { |
| EncHashTransporter* pResult = NULL; |
| if( xRef.is() ) |
| { |
| uno::Any aMat( xRef->getMaterial() ); |
| sal_Int64 nMat = 0; |
| if( aMat >>= nMat ) |
| { |
| std::map< sal_IntPtr, EncHashTransporter* >::iterator it = sTransporters.find( static_cast<sal_IntPtr>(nMat) ); |
| if( it != sTransporters.end() ) |
| pResult = it->second; |
| } |
| } |
| return pResult; |
| } |
| |
| sal_Bool PDFWriterImpl::checkEncryptionBufferSize( register sal_Int32 newSize ) |
| { |
| if( m_nEncryptionBufferSize < newSize ) |
| { |
| /* reallocate the buffer, the used function allocate as rtl_allocateMemory |
| if the pointer parameter is NULL */ |
| m_pEncryptionBuffer = (sal_uInt8*)rtl_reallocateMemory( m_pEncryptionBuffer, newSize ); |
| if( m_pEncryptionBuffer ) |
| m_nEncryptionBufferSize = newSize; |
| else |
| m_nEncryptionBufferSize = 0; |
| } |
| return ( m_nEncryptionBufferSize != 0 ); |
| } |
| |
| void PDFWriterImpl::checkAndEnableStreamEncryption( register sal_Int32 nObject ) |
| { |
| if( m_aContext.Encryption.Encrypt() ) |
| { |
| m_bEncryptThisStream = true; |
| sal_Int32 i = m_nKeyLength; |
| m_aContext.Encryption.EncryptionKey[i++] = (sal_uInt8)nObject; |
| m_aContext.Encryption.EncryptionKey[i++] = (sal_uInt8)( nObject >> 8 ); |
| m_aContext.Encryption.EncryptionKey[i++] = (sal_uInt8)( nObject >> 16 ); |
| //the other location of m_nEncryptionKey are already set to 0, our fixed generation number |
| // do the MD5 hash |
| sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ]; |
| // the i+2 to take into account the generation number, always zero |
| rtl_digest_MD5( &m_aContext.Encryption.EncryptionKey[0], i+2, nMD5Sum, sizeof(nMD5Sum) ); |
| // initialize the RC4 with the key |
| // key legth: see algoritm 3.1, step 4: (N+5) max 16 |
| rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode, nMD5Sum, m_nRC4KeyLength, NULL, 0 ); |
| } |
| } |
| |
| void PDFWriterImpl::enableStringEncryption( register sal_Int32 nObject ) |
| { |
| if( m_aContext.Encryption.Encrypt() ) |
| { |
| sal_Int32 i = m_nKeyLength; |
| m_aContext.Encryption.EncryptionKey[i++] = (sal_uInt8)nObject; |
| m_aContext.Encryption.EncryptionKey[i++] = (sal_uInt8)( nObject >> 8 ); |
| m_aContext.Encryption.EncryptionKey[i++] = (sal_uInt8)( nObject >> 16 ); |
| //the other location of m_nEncryptionKey are already set to 0, our fixed generation number |
| // do the MD5 hash |
| sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ]; |
| // the i+2 to take into account the generation number, always zero |
| rtl_digest_MD5( &m_aContext.Encryption.EncryptionKey[0], i+2, nMD5Sum, sizeof(nMD5Sum) ); |
| // initialize the RC4 with the key |
| // key legth: see algoritm 3.1, step 4: (N+5) max 16 |
| rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode, nMD5Sum, m_nRC4KeyLength, NULL, 0 ); |
| } |
| } |
| |
| /* init the encryption engine |
| 1. init the document id, used both for building the document id and for building the encryption key(s) |
| 2. build the encryption key following algorithms described in the PDF specification |
| */ |
| uno::Reference< beans::XMaterialHolder > PDFWriterImpl::initEncryption( const rtl::OUString& i_rOwnerPassword, |
| const rtl::OUString& i_rUserPassword, |
| bool b128Bit |
| ) |
| { |
| uno::Reference< beans::XMaterialHolder > xResult; |
| if( i_rOwnerPassword.getLength() || i_rUserPassword.getLength() ) |
| { |
| EncHashTransporter* pTransporter = new EncHashTransporter; |
| xResult = pTransporter; |
| |
| // get padded passwords |
| sal_uInt8 aPadUPW[ENCRYPTED_PWD_SIZE], aPadOPW[ENCRYPTED_PWD_SIZE]; |
| padPassword( i_rOwnerPassword.getLength() ? i_rOwnerPassword : i_rUserPassword, aPadOPW ); |
| padPassword( i_rUserPassword, aPadUPW ); |
| sal_Int32 nKeyLength = SECUR_40BIT_KEY; |
| if( b128Bit ) |
| nKeyLength = SECUR_128BIT_KEY; |
| |
| if( computeODictionaryValue( aPadOPW, aPadUPW, pTransporter->getOValue(), nKeyLength ) ) |
| { |
| rtlDigest aDig = pTransporter->getUDigest(); |
| if( rtl_digest_updateMD5( aDig, aPadUPW, ENCRYPTED_PWD_SIZE ) != rtl_Digest_E_None ) |
| xResult.clear(); |
| } |
| else |
| xResult.clear(); |
| |
| // trash temporary padded cleartext PWDs |
| rtl_zeroMemory( aPadOPW, sizeof(aPadOPW) ); |
| rtl_zeroMemory( aPadUPW, sizeof(aPadUPW) ); |
| |
| } |
| return xResult; |
| } |
| |
| bool PDFWriterImpl::prepareEncryption( const uno::Reference< beans::XMaterialHolder >& xEnc ) |
| { |
| bool bSuccess = false; |
| EncHashTransporter* pTransporter = EncHashTransporter::getEncHashTransporter( xEnc ); |
| if( pTransporter ) |
| { |
| sal_Int32 nKeyLength = 0, nRC4KeyLength = 0; |
| sal_Int32 nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, nKeyLength, nRC4KeyLength ); |
| m_aContext.Encryption.OValue = pTransporter->getOValue(); |
| bSuccess = computeUDictionaryValue( pTransporter, m_aContext.Encryption, nKeyLength, nAccessPermissions ); |
| } |
| if( ! bSuccess ) |
| { |
| m_aContext.Encryption.OValue.clear(); |
| m_aContext.Encryption.UValue.clear(); |
| m_aContext.Encryption.EncryptionKey.clear(); |
| } |
| return bSuccess; |
| } |
| |
| sal_Int32 PDFWriterImpl::computeAccessPermissions( const vcl::PDFWriter::PDFEncryptionProperties& i_rProperties, |
| sal_Int32& o_rKeyLength, sal_Int32& o_rRC4KeyLength ) |
| { |
| /* |
| 2) compute the access permissions, in numerical form |
| |
| the default value depends on the revision 2 (40 bit) or 3 (128 bit security): |
| - for 40 bit security the unused bit must be set to 1, since they are not used |
| - for 128 bit security the same bit must be preset to 0 and set later if needed |
| according to the table 3.15, pdf v 1.4 */ |
| sal_Int32 nAccessPermissions = ( i_rProperties.Security128bit ) ? 0xfffff0c0 : 0xffffffc0 ; |
| |
| /* check permissions for 40 bit security case */ |
| nAccessPermissions |= ( i_rProperties.CanPrintTheDocument ) ? 1 << 2 : 0; |
| nAccessPermissions |= ( i_rProperties.CanModifyTheContent ) ? 1 << 3 : 0; |
| nAccessPermissions |= ( i_rProperties.CanCopyOrExtract ) ? 1 << 4 : 0; |
| nAccessPermissions |= ( i_rProperties.CanAddOrModify ) ? 1 << 5 : 0; |
| o_rKeyLength = SECUR_40BIT_KEY; |
| o_rRC4KeyLength = SECUR_40BIT_KEY+5; // for this value see PDF spec v 1.4, algorithm 3.1 step 4, where n is 5 |
| |
| if( i_rProperties.Security128bit ) |
| { |
| o_rKeyLength = SECUR_128BIT_KEY; |
| o_rRC4KeyLength = 16; // for this value see PDF spec v 1.4, algorithm 3.1 step 4, where n is 16, thus maximum |
| // permitted value is 16 |
| nAccessPermissions |= ( i_rProperties.CanFillInteractive ) ? 1 << 8 : 0; |
| nAccessPermissions |= ( i_rProperties.CanExtractForAccessibility ) ? 1 << 9 : 0; |
| nAccessPermissions |= ( i_rProperties.CanAssemble ) ? 1 << 10 : 0; |
| nAccessPermissions |= ( i_rProperties.CanPrintFull ) ? 1 << 11 : 0; |
| } |
| return nAccessPermissions; |
| } |
| |
| /************************************************************* |
| begin i12626 methods |
| |
| Implements Algorithm 3.2, step 1 only |
| */ |
| void PDFWriterImpl::padPassword( const rtl::OUString& i_rPassword, sal_uInt8* o_pPaddedPW ) |
| { |
| // get ansi-1252 version of the password string CHECKIT ! i12626 |
| rtl::OString aString( rtl::OUStringToOString( i_rPassword, RTL_TEXTENCODING_MS_1252 ) ); |
| |
| //copy the string to the target |
| sal_Int32 nToCopy = ( aString.getLength() < ENCRYPTED_PWD_SIZE ) ? aString.getLength() : ENCRYPTED_PWD_SIZE; |
| sal_Int32 nCurrentChar; |
| |
| for( nCurrentChar = 0; nCurrentChar < nToCopy; nCurrentChar++ ) |
| o_pPaddedPW[nCurrentChar] = (sal_uInt8)( aString.getStr()[nCurrentChar] ); |
| |
| //pad it with standard byte string |
| sal_Int32 i,y; |
| for( i = nCurrentChar, y = 0 ; i < ENCRYPTED_PWD_SIZE; i++, y++ ) |
| o_pPaddedPW[i] = s_nPadString[y]; |
| |
| // trash memory of temporary clear text password |
| rtl_zeroMemory( (sal_Char*)aString.getStr(), aString.getLength() ); |
| } |
| |
| /********************************** |
| Algorithm 3.2 Compute the encryption key used |
| |
| step 1 should already be done before calling, the paThePaddedPassword parameter should contain |
| the padded password and must be 32 byte long, the encryption key is returned into the paEncryptionKey parameter, |
| it will be 16 byte long for 128 bit security; for 40 bit security only the first 5 bytes are used |
| |
| TODO: in pdf ver 1.5 and 1.6 the step 6 is different, should be implemented. See spec. |
| |
| */ |
| bool PDFWriterImpl::computeEncryptionKey( EncHashTransporter* i_pTransporter, vcl::PDFWriter::PDFEncryptionProperties& io_rProperties, sal_Int32 i_nAccessPermissions ) |
| { |
| bool bSuccess = true; |
| sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ]; |
| |
| // transporter contains an MD5 digest with the padded user password already |
| rtlDigest aDigest = i_pTransporter->getUDigest(); |
| rtlDigestError nError = rtl_Digest_E_None; |
| if( aDigest ) |
| { |
| //step 3 |
| if( ! io_rProperties.OValue.empty() ) |
| nError = rtl_digest_updateMD5( aDigest, &io_rProperties.OValue[0] , sal_Int32(io_rProperties.OValue.size()) ); |
| else |
| bSuccess = false; |
| //Step 4 |
| sal_uInt8 nPerm[4]; |
| |
| nPerm[0] = (sal_uInt8)i_nAccessPermissions; |
| nPerm[1] = (sal_uInt8)( i_nAccessPermissions >> 8 ); |
| nPerm[2] = (sal_uInt8)( i_nAccessPermissions >> 16 ); |
| nPerm[3] = (sal_uInt8)( i_nAccessPermissions >> 24 ); |
| |
| if( nError == rtl_Digest_E_None ) |
| nError = rtl_digest_updateMD5( aDigest, nPerm , sizeof( nPerm ) ); |
| |
| //step 5, get the document ID, binary form |
| if( nError == rtl_Digest_E_None ) |
| nError = rtl_digest_updateMD5( aDigest, &io_rProperties.DocumentIdentifier[0], sal_Int32(io_rProperties.DocumentIdentifier.size()) ); |
| //get the digest |
| if( nError == rtl_Digest_E_None ) |
| { |
| rtl_digest_getMD5( aDigest, nMD5Sum, sizeof( nMD5Sum ) ); |
| |
| //step 6, only if 128 bit |
| if( io_rProperties.Security128bit ) |
| { |
| for( sal_Int32 i = 0; i < 50; i++ ) |
| { |
| nError = rtl_digest_updateMD5( aDigest, &nMD5Sum, sizeof( nMD5Sum ) ); |
| if( nError != rtl_Digest_E_None ) |
| { |
| bSuccess = false; |
| break; |
| } |
| rtl_digest_getMD5( aDigest, nMD5Sum, sizeof( nMD5Sum ) ); |
| } |
| } |
| } |
| } |
| else |
| bSuccess = false; |
| |
| i_pTransporter->invalidate(); |
| |
| //Step 7 |
| if( bSuccess ) |
| { |
| io_rProperties.EncryptionKey.resize( MAXIMUM_RC4_KEY_LENGTH ); |
| for( sal_Int32 i = 0; i < MD5_DIGEST_SIZE; i++ ) |
| io_rProperties.EncryptionKey[i] = nMD5Sum[i]; |
| } |
| else |
| io_rProperties.EncryptionKey.clear(); |
| |
| return bSuccess; |
| } |
| |
| /********************************** |
| Algorithm 3.3 Compute the encryption dictionary /O value, save into the class data member |
| the step numbers down here correspond to the ones in PDF v.1.4 specfication |
| */ |
| bool PDFWriterImpl::computeODictionaryValue( const sal_uInt8* i_pPaddedOwnerPassword, |
| const sal_uInt8* i_pPaddedUserPassword, |
| std::vector< sal_uInt8 >& io_rOValue, |
| sal_Int32 i_nKeyLength |
| ) |
| { |
| bool bSuccess = true; |
| |
| io_rOValue.resize( ENCRYPTED_PWD_SIZE ); |
| |
| rtlDigest aDigest = rtl_digest_createMD5(); |
| rtlCipher aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream ); |
| if( aDigest && aCipher) |
| { |
| //step 1 already done, data is in i_pPaddedOwnerPassword |
| //step 2 |
| |
| rtlDigestError nError = rtl_digest_updateMD5( aDigest, i_pPaddedOwnerPassword, ENCRYPTED_PWD_SIZE ); |
| if( nError == rtl_Digest_E_None ) |
| { |
| sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ]; |
| |
| rtl_digest_getMD5( aDigest, nMD5Sum, sizeof(nMD5Sum) ); |
| //step 3, only if 128 bit |
| if( i_nKeyLength == SECUR_128BIT_KEY ) |
| { |
| sal_Int32 i; |
| for( i = 0; i < 50; i++ ) |
| { |
| nError = rtl_digest_updateMD5( aDigest, nMD5Sum, sizeof( nMD5Sum ) ); |
| if( nError != rtl_Digest_E_None ) |
| { |
| bSuccess = false; |
| break; |
| } |
| rtl_digest_getMD5( aDigest, nMD5Sum, sizeof( nMD5Sum ) ); |
| } |
| } |
| //Step 4, the key is in nMD5Sum |
| //step 5 already done, data is in i_pPaddedUserPassword |
| //step 6 |
| rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode, |
| nMD5Sum, i_nKeyLength , NULL, 0 ); |
| // encrypt the user password using the key set above |
| rtl_cipher_encodeARCFOUR( aCipher, i_pPaddedUserPassword, ENCRYPTED_PWD_SIZE, // the data to be encrypted |
| &io_rOValue[0], sal_Int32(io_rOValue.size()) ); //encrypted data |
| //Step 7, only if 128 bit |
| if( i_nKeyLength == SECUR_128BIT_KEY ) |
| { |
| sal_uInt32 i, y; |
| sal_uInt8 nLocalKey[ SECUR_128BIT_KEY ]; // 16 = 128 bit key |
| |
| for( i = 1; i <= 19; i++ ) // do it 19 times, start with 1 |
| { |
| for( y = 0; y < sizeof( nLocalKey ); y++ ) |
| nLocalKey[y] = (sal_uInt8)( nMD5Sum[y] ^ i ); |
| |
| rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode, |
| nLocalKey, SECUR_128BIT_KEY, NULL, 0 ); //destination data area, on init can be NULL |
| rtl_cipher_encodeARCFOUR( aCipher, &io_rOValue[0], sal_Int32(io_rOValue.size()), // the data to be encrypted |
| &io_rOValue[0], sal_Int32(io_rOValue.size()) ); // encrypted data, can be the same as the input, encrypt "in place" |
| //step 8, store in class data member |
| } |
| } |
| } |
| else |
| bSuccess = false; |
| } |
| else |
| bSuccess = false; |
| |
| if( aDigest ) |
| rtl_digest_destroyMD5( aDigest ); |
| if( aCipher ) |
| rtl_cipher_destroyARCFOUR( aCipher ); |
| |
| if( ! bSuccess ) |
| io_rOValue.clear(); |
| return bSuccess; |
| } |
| |
| /********************************** |
| Algorithms 3.4 and 3.5 Compute the encryption dictionary /U value, save into the class data member, revision 2 (40 bit) or 3 (128 bit) |
| */ |
| bool PDFWriterImpl::computeUDictionaryValue( EncHashTransporter* i_pTransporter, |
| vcl::PDFWriter::PDFEncryptionProperties& io_rProperties, |
| sal_Int32 i_nKeyLength, |
| sal_Int32 i_nAccessPermissions |
| ) |
| { |
| bool bSuccess = true; |
| |
| io_rProperties.UValue.resize( ENCRYPTED_PWD_SIZE ); |
| |
| rtlDigest aDigest = rtl_digest_createMD5(); |
| rtlCipher aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream ); |
| if( aDigest && aCipher ) |
| { |
| //step 1, common to both 3.4 and 3.5 |
| if( computeEncryptionKey( i_pTransporter, io_rProperties, i_nAccessPermissions ) ) |
| { |
| // prepare encryption key for object |
| for( sal_Int32 i = i_nKeyLength, y = 0; y < 5 ; y++ ) |
| io_rProperties.EncryptionKey[i++] = 0; |
| |
| if( io_rProperties.Security128bit == false ) |
| { |
| //3.4 |
| //step 2 and 3 |
| rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode, |
| &io_rProperties.EncryptionKey[0], 5 , // key and key length |
| NULL, 0 ); //destination data area |
| // encrypt the user password using the key set above, save for later use |
| rtl_cipher_encodeARCFOUR( aCipher, s_nPadString, sizeof( s_nPadString ), // the data to be encrypted |
| &io_rProperties.UValue[0], sal_Int32(io_rProperties.UValue.size()) ); //encrypted data, stored in class data member |
| } |
| else |
| { |
| //or 3.5, for 128 bit security |
| //step6, initilize the last 16 bytes of the encrypted user password to 0 |
| for(sal_uInt32 i = MD5_DIGEST_SIZE; i < sal_uInt32(io_rProperties.UValue.size()); i++) |
| io_rProperties.UValue[i] = 0; |
| //step 2 |
| rtlDigestError nError = rtl_digest_updateMD5( aDigest, s_nPadString, sizeof( s_nPadString ) ); |
| //step 3 |
| if( nError == rtl_Digest_E_None ) |
| nError = rtl_digest_updateMD5( aDigest, &io_rProperties.DocumentIdentifier[0], sal_Int32(io_rProperties.DocumentIdentifier.size()) ); |
| else |
| bSuccess = false; |
| |
| sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ]; |
| rtl_digest_getMD5( aDigest, nMD5Sum, sizeof(nMD5Sum) ); |
| //Step 4 |
| rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode, |
| &io_rProperties.EncryptionKey[0], SECUR_128BIT_KEY, NULL, 0 ); //destination data area |
| rtl_cipher_encodeARCFOUR( aCipher, nMD5Sum, sizeof( nMD5Sum ), // the data to be encrypted |
| &io_rProperties.UValue[0], sizeof( nMD5Sum ) ); //encrypted data, stored in class data member |
| //step 5 |
| sal_uInt32 i, y; |
| sal_uInt8 nLocalKey[SECUR_128BIT_KEY]; |
| |
| for( i = 1; i <= 19; i++ ) // do it 19 times, start with 1 |
| { |
| for( y = 0; y < sizeof( nLocalKey ) ; y++ ) |
| nLocalKey[y] = (sal_uInt8)( io_rProperties.EncryptionKey[y] ^ i ); |
| |
| rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode, |
| nLocalKey, SECUR_128BIT_KEY, // key and key length |
| NULL, 0 ); //destination data area, on init can be NULL |
| rtl_cipher_encodeARCFOUR( aCipher, &io_rProperties.UValue[0], SECUR_128BIT_KEY, // the data to be encrypted |
| &io_rProperties.UValue[0], SECUR_128BIT_KEY ); // encrypted data, can be the same as the input, encrypt "in place" |
| } |
| } |
| } |
| else |
| bSuccess = false; |
| } |
| else |
| bSuccess = false; |
| |
| if( aDigest ) |
| rtl_digest_destroyMD5( aDigest ); |
| if( aCipher ) |
| rtl_cipher_destroyARCFOUR( aCipher ); |
| |
| if( ! bSuccess ) |
| io_rProperties.UValue.clear(); |
| return bSuccess; |
| } |
| |
| /* end i12626 methods */ |
| |
| static const long unsetRun[256] = |
| { |
| 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, /* 0x00 - 0x0f */ |
| 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x10 - 0x1f */ |
| 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x20 - 0x2f */ |
| 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x30 - 0x3f */ |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 - 0x4f */ |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x50 - 0x5f */ |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 - 0x6f */ |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x70 - 0x7f */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xf0 - 0xff */ |
| }; |
| |
| static const long setRun[256] = |
| { |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 - 0x0f */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2f */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x6f */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7f */ |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x80 - 0x8f */ |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x90 - 0x9f */ |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xa0 - 0xaf */ |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xb0 - 0xbf */ |
| 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xc0 - 0xcf */ |
| 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xd0 - 0xdf */ |
| 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xe0 - 0xef */ |
| 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8, /* 0xf0 - 0xff */ |
| }; |
| |
| inline bool isSet( const Scanline i_pLine, long i_nIndex ) |
| { |
| return (i_pLine[ i_nIndex/8 ] & (0x80 >> (i_nIndex&7))) != 0; |
| } |
| |
| long findBitRun( const Scanline i_pLine, long i_nStartIndex, long i_nW, bool i_bSet ) |
| { |
| if( i_nStartIndex < 0 ) |
| return i_nW; |
| |
| long nIndex = i_nStartIndex; |
| if( nIndex < i_nW ) |
| { |
| const sal_uInt8 * pByte = static_cast<sal_uInt8*>(i_pLine) + (nIndex/8); |
| sal_uInt8 nByte = *pByte; |
| |
| // run up to byte boundary |
| long nBitInByte = (nIndex & 7); |
| if( nBitInByte ) |
| { |
| sal_uInt8 nMask = 0x80 >> nBitInByte; |
| while( nBitInByte != 8 ) |
| { |
| if( (nByte & nMask) != (i_bSet ? nMask : 0) ) |
| return nIndex < i_nW ? nIndex : i_nW; |
| nMask = nMask >> 1; |
| nBitInByte++; |
| nIndex++; |
| } |
| if( nIndex < i_nW ) |
| { |
| pByte++; |
| nByte = *pByte; |
| } |
| } |
| |
| sal_uInt8 nRunByte; |
| const long* pRunTable; |
| if( i_bSet ) |
| { |
| nRunByte = 0xff; |
| pRunTable = setRun; |
| } |
| else |
| { |
| nRunByte = 0; |
| pRunTable = unsetRun; |
| } |
| |
| while( nByte == nRunByte && nIndex < i_nW ) |
| { |
| nIndex += 8; |
| pByte++; |
| nByte = *pByte; |
| } |
| if( nIndex < i_nW ) |
| { |
| nIndex += pRunTable[nByte]; |
| } |
| } |
| return nIndex < i_nW ? nIndex : i_nW; |
| } |
| |
| struct BitStreamState |
| { |
| sal_uInt8 mnBuffer; |
| sal_uInt32 mnNextBitPos; |
| |
| BitStreamState() |
| : mnBuffer( 0 ) |
| , mnNextBitPos( 8 ) |
| { |
| } |
| |
| const sal_uInt8* getByte() const { return &mnBuffer; } |
| void flush() { mnNextBitPos = 8; mnBuffer = 0; } |
| }; |
| |
| void PDFWriterImpl::putG4Bits( sal_uInt32 i_nLength, sal_uInt32 i_nCode, BitStreamState& io_rState ) |
| { |
| while( i_nLength > io_rState.mnNextBitPos ) |
| { |
| io_rState.mnBuffer |= static_cast<sal_uInt8>( i_nCode >> (i_nLength - io_rState.mnNextBitPos) ); |
| i_nLength -= io_rState.mnNextBitPos; |
| writeBuffer( io_rState.getByte(), 1 ); |
| io_rState.flush(); |
| } |
| OSL_ASSERT( i_nLength < 9 ); |
| static const unsigned int msbmask[9] = { 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff }; |
| io_rState.mnBuffer |= static_cast<sal_uInt8>( (i_nCode & msbmask[i_nLength]) << (io_rState.mnNextBitPos - i_nLength) ); |
| io_rState.mnNextBitPos -= i_nLength; |
| if( io_rState.mnNextBitPos == 0 ) |
| { |
| writeBuffer( io_rState.getByte(), 1 ); |
| io_rState.flush(); |
| } |
| } |
| |
| struct PixelCode |
| { |
| sal_uInt32 mnEncodedPixels; |
| sal_uInt32 mnCodeBits; |
| sal_uInt32 mnCode; |
| }; |
| |
| static const PixelCode WhitePixelCodes[] = |
| { |
| { 0, 8, 0x35 }, // 0011 0101 |
| { 1, 6, 0x7 }, // 0001 11 |
| { 2, 4, 0x7 }, // 0111 |
| { 3, 4, 0x8 }, // 1000 |
| { 4, 4, 0xB }, // 1011 |
| { 5, 4, 0xC }, // 1100 |
| { 6, 4, 0xE }, // 1110 |
| { 7, 4, 0xF }, // 1111 |
| { 8, 5, 0x13 }, // 1001 1 |
| { 9, 5, 0x14 }, // 1010 0 |
| { 10, 5, 0x7 }, // 0011 1 |
| { 11, 5, 0x8 }, // 0100 0 |
| { 12, 6, 0x8 }, // 0010 00 |
| { 13, 6, 0x3 }, // 0000 11 |
| { 14, 6, 0x34 }, // 1101 00 |
| { 15, 6, 0x35 }, // 1101 01 |
| { 16, 6, 0x2A }, // 1010 10 |
| { 17, 6, 0x2B }, // 1010 11 |
| { 18, 7, 0x27 }, // 0100 111 |
| { 19, 7, 0xC }, // 0001 100 |
| { 20, 7, 0x8 }, // 0001 000 |
| { 21, 7, 0x17 }, // 0010 111 |
| { 22, 7, 0x3 }, // 0000 011 |
| { 23, 7, 0x4 }, // 0000 100 |
| { 24, 7, 0x28 }, // 0101 000 |
| { 25, 7, 0x2B }, // 0101 011 |
| { 26, 7, 0x13 }, // 0010 011 |
| { 27, 7, 0x24 }, // 0100 100 |
| { 28, 7, 0x18 }, // 0011 000 |
| { 29, 8, 0x2 }, // 0000 0010 |
| { 30, 8, 0x3 }, // 0000 0011 |
| { 31, 8, 0x1A }, // 0001 1010 |
| { 32, 8, 0x1B }, // 0001 1011 |
| { 33, 8, 0x12 }, // 0001 0010 |
| { 34, 8, 0x13 }, // 0001 0011 |
| { 35, 8, 0x14 }, // 0001 0100 |
| { 36, 8, 0x15 }, // 0001 0101 |
| { 37, 8, 0x16 }, // 0001 0110 |
| { 38, 8, 0x17 }, // 0001 0111 |
| { 39, 8, 0x28 }, // 0010 1000 |
| { 40, 8, 0x29 }, // 0010 1001 |
| { 41, 8, 0x2A }, // 0010 1010 |
| { 42, 8, 0x2B }, // 0010 1011 |
| { 43, 8, 0x2C }, // 0010 1100 |
| { 44, 8, 0x2D }, // 0010 1101 |
| { 45, 8, 0x4 }, // 0000 0100 |
| { 46, 8, 0x5 }, // 0000 0101 |
| { 47, 8, 0xA }, // 0000 1010 |
| { 48, 8, 0xB }, // 0000 1011 |
| { 49, 8, 0x52 }, // 0101 0010 |
| { 50, 8, 0x53 }, // 0101 0011 |
| { 51, 8, 0x54 }, // 0101 0100 |
| { 52, 8, 0x55 }, // 0101 0101 |
| { 53, 8, 0x24 }, // 0010 0100 |
| { 54, 8, 0x25 }, // 0010 0101 |
| { 55, 8, 0x58 }, // 0101 1000 |
| { 56, 8, 0x59 }, // 0101 1001 |
| { 57, 8, 0x5A }, // 0101 1010 |
| { 58, 8, 0x5B }, // 0101 1011 |
| { 59, 8, 0x4A }, // 0100 1010 |
| { 60, 8, 0x4B }, // 0100 1011 |
| { 61, 8, 0x32 }, // 0011 0010 |
| { 62, 8, 0x33 }, // 0011 0011 |
| { 63, 8, 0x34 }, // 0011 0100 |
| { 64, 5, 0x1B }, // 1101 1 |
| { 128, 5, 0x12 }, // 1001 0 |
| { 192, 6, 0x17 }, // 0101 11 |
| { 256, 7, 0x37 }, // 0110 111 |
| { 320, 8, 0x36 }, // 0011 0110 |
| { 384, 8, 0x37 }, // 0011 0111 |
| { 448, 8, 0x64 }, // 0110 0100 |
| { 512, 8, 0x65 }, // 0110 0101 |
| { 576, 8, 0x68 }, // 0110 1000 |
| { 640, 8, 0x67 }, // 0110 0111 |
| { 704, 9, 0xCC }, // 0110 0110 0 |
| { 768, 9, 0xCD }, // 0110 0110 1 |
| { 832, 9, 0xD2 }, // 0110 1001 0 |
| { 896, 9, 0xD3 }, // 0110 1001 1 |
| { 960, 9, 0xD4 }, // 0110 1010 0 |
| { 1024, 9, 0xD5 }, // 0110 1010 1 |
| { 1088, 9, 0xD6 }, // 0110 1011 0 |
| { 1152, 9, 0xD7 }, // 0110 1011 1 |
| { 1216, 9, 0xD8 }, // 0110 1100 0 |
| { 1280, 9, 0xD9 }, // 0110 1100 1 |
| { 1344, 9, 0xDA }, // 0110 1101 0 |
| { 1408, 9, 0xDB }, // 0110 1101 1 |
| { 1472, 9, 0x98 }, // 0100 1100 0 |
| { 1536, 9, 0x99 }, // 0100 1100 1 |
| { 1600, 9, 0x9A }, // 0100 1101 0 |
| { 1664, 6, 0x18 }, // 0110 00 |
| { 1728, 9, 0x9B }, // 0100 1101 1 |
| { 1792, 11, 0x8 }, // 0000 0001 000 |
| { 1856, 11, 0xC }, // 0000 0001 100 |
| { 1920, 11, 0xD }, // 0000 0001 101 |
| { 1984, 12, 0x12 }, // 0000 0001 0010 |
| { 2048, 12, 0x13 }, // 0000 0001 0011 |
| { 2112, 12, 0x14 }, // 0000 0001 0100 |
| { 2176, 12, 0x15 }, // 0000 0001 0101 |
| { 2240, 12, 0x16 }, // 0000 0001 0110 |
| { 2304, 12, 0x17 }, // 0000 0001 0111 |
| { 2368, 12, 0x1C }, // 0000 0001 1100 |
| { 2432, 12, 0x1D }, // 0000 0001 1101 |
| { 2496, 12, 0x1E }, // 0000 0001 1110 |
| { 2560, 12, 0x1F } // 0000 0001 1111 |
| }; |
| |
| static const PixelCode BlackPixelCodes[] = |
| { |
| { 0, 10, 0x37 }, // 0000 1101 11 |
| { 1, 3, 0x2 }, // 010 |
| { 2, 2, 0x3 }, // 11 |
| { 3, 2, 0x2 }, // 10 |
| { 4, 3, 0x3 }, // 011 |
| { 5, 4, 0x3 }, // 0011 |
| { 6, 4, 0x2 }, // 0010 |
| { 7, 5, 0x3 }, // 0001 1 |
| { 8, 6, 0x5 }, // 0001 01 |
| { 9, 6, 0x4 }, // 0001 00 |
| { 10, 7, 0x4 }, // 0000 100 |
| { 11, 7, 0x5 }, // 0000 101 |
| { 12, 7, 0x7 }, // 0000 111 |
| { 13, 8, 0x4 }, // 0000 0100 |
| { 14, 8, 0x7 }, // 0000 0111 |
| { 15, 9, 0x18 }, // 0000 1100 0 |
| { 16, 10, 0x17 }, // 0000 0101 11 |
| { 17, 10, 0x18 }, // 0000 0110 00 |
| { 18, 10, 0x8 }, // 0000 0010 00 |
| { 19, 11, 0x67 }, // 0000 1100 111 |
| { 20, 11, 0x68 }, // 0000 1101 000 |
| { 21, 11, 0x6C }, // 0000 1101 100 |
| { 22, 11, 0x37 }, // 0000 0110 111 |
| { 23, 11, 0x28 }, // 0000 0101 000 |
| { 24, 11, 0x17 }, // 0000 0010 111 |
| { 25, 11, 0x18 }, // 0000 0011 000 |
| { 26, 12, 0xCA }, // 0000 1100 1010 |
| { 27, 12, 0xCB }, // 0000 1100 1011 |
| { 28, 12, 0xCC }, // 0000 1100 1100 |
| { 29, 12, 0xCD }, // 0000 1100 1101 |
| { 30, 12, 0x68 }, // 0000 0110 1000 |
| { 31, 12, 0x69 }, // 0000 0110 1001 |
| { 32, 12, 0x6A }, // 0000 0110 1010 |
| { 33, 12, 0x6B }, // 0000 0110 1011 |
| { 34, 12, 0xD2 }, // 0000 1101 0010 |
| { 35, 12, 0xD3 }, // 0000 1101 0011 |
| { 36, 12, 0xD4 }, // 0000 1101 0100 |
| { 37, 12, 0xD5 }, // 0000 1101 0101 |
| { 38, 12, 0xD6 }, // 0000 1101 0110 |
| { 39, 12, 0xD7 }, // 0000 1101 0111 |
| { 40, 12, 0x6C }, // 0000 0110 1100 |
| { 41, 12, 0x6D }, // 0000 0110 1101 |
| { 42, 12, 0xDA }, // 0000 1101 1010 |
| { 43, 12, 0xDB }, // 0000 1101 1011 |
| { 44, 12, 0x54 }, // 0000 0101 0100 |
| { 45, 12, 0x55 }, // 0000 0101 0101 |
| { 46, 12, 0x56 }, // 0000 0101 0110 |
| { 47, 12, 0x57 }, // 0000 0101 0111 |
| { 48, 12, 0x64 }, // 0000 0110 0100 |
| { 49, 12, 0x65 }, // 0000 0110 0101 |
| { 50, 12, 0x52 }, // 0000 0101 0010 |
| { 51, 12, 0x53 }, // 0000 0101 0011 |
| { 52, 12, 0x24 }, // 0000 0010 0100 |
| { 53, 12, 0x37 }, // 0000 0011 0111 |
| { 54, 12, 0x38 }, // 0000 0011 1000 |
| { 55, 12, 0x27 }, // 0000 0010 0111 |
| { 56, 12, 0x28 }, // 0000 0010 1000 |
| { 57, 12, 0x58 }, // 0000 0101 1000 |
| { 58, 12, 0x59 }, // 0000 0101 1001 |
| { 59, 12, 0x2B }, // 0000 0010 1011 |
| { 60, 12, 0x2C }, // 0000 0010 1100 |
| { 61, 12, 0x5A }, // 0000 0101 1010 |
| { 62, 12, 0x66 }, // 0000 0110 0110 |
| { 63, 12, 0x67 }, // 0000 0110 0111 |
| { 64, 10, 0xF }, // 0000 0011 11 |
| { 128, 12, 0xC8 }, // 0000 1100 1000 |
| { 192, 12, 0xC9 }, // 0000 1100 1001 |
| { 256, 12, 0x5B }, // 0000 0101 1011 |
| { 320, 12, 0x33 }, // 0000 0011 0011 |
| { 384, 12, 0x34 }, // 0000 0011 0100 |
| { 448, 12, 0x35 }, // 0000 0011 0101 |
| { 512, 13, 0x6C }, // 0000 0011 0110 0 |
| { 576, 13, 0x6D }, // 0000 0011 0110 1 |
| { 640, 13, 0x4A }, // 0000 0010 0101 0 |
| { 704, 13, 0x4B }, // 0000 0010 0101 1 |
| { 768, 13, 0x4C }, // 0000 0010 0110 0 |
| { 832, 13, 0x4D }, // 0000 0010 0110 1 |
| { 896, 13, 0x72 }, // 0000 0011 1001 0 |
| { 960, 13, 0x73 }, // 0000 0011 1001 1 |
| { 1024, 13, 0x74 }, // 0000 0011 1010 0 |
| { 1088, 13, 0x75 }, // 0000 0011 1010 1 |
| { 1152, 13, 0x76 }, // 0000 0011 1011 0 |
| { 1216, 13, 0x77 }, // 0000 0011 1011 1 |
| { 1280, 13, 0x52 }, // 0000 0010 1001 0 |
| { 1344, 13, 0x53 }, // 0000 0010 1001 1 |
| { 1408, 13, 0x54 }, // 0000 0010 1010 0 |
| { 1472, 13, 0x55 }, // 0000 0010 1010 1 |
| { 1536, 13, 0x5A }, // 0000 0010 1101 0 |
| { 1600, 13, 0x5B }, // 0000 0010 1101 1 |
| { 1664, 13, 0x64 }, // 0000 0011 0010 0 |
| { 1728, 13, 0x65 }, // 0000 0011 0010 1 |
| { 1792, 11, 0x8 }, // 0000 0001 000 |
| { 1856, 11, 0xC }, // 0000 0001 100 |
| { 1920, 11, 0xD }, // 0000 0001 101 |
| { 1984, 12, 0x12 }, // 0000 0001 0010 |
| { 2048, 12, 0x13 }, // 0000 0001 0011 |
| { 2112, 12, 0x14 }, // 0000 0001 0100 |
| { 2176, 12, 0x15 }, // 0000 0001 0101 |
| { 2240, 12, 0x16 }, // 0000 0001 0110 |
| { 2304, 12, 0x17 }, // 0000 0001 0111 |
| { 2368, 12, 0x1C }, // 0000 0001 1100 |
| { 2432, 12, 0x1D }, // 0000 0001 1101 |
| { 2496, 12, 0x1E }, // 0000 0001 1110 |
| { 2560, 12, 0x1F } // 0000 0001 1111 |
| }; |
| |
| |
| void PDFWriterImpl::putG4Span( long i_nSpan, bool i_bWhitePixel, BitStreamState& io_rState ) |
| { |
| const PixelCode* pTable = i_bWhitePixel ? WhitePixelCodes : BlackPixelCodes; |
| // maximum encoded span is 2560 consecutive pixels |
| while( i_nSpan > 2623 ) |
| { |
| // write 2560 bits, that is entry (63 + (2560 >> 6)) == 103 in the appropriate table |
| putG4Bits( pTable[103].mnCodeBits, pTable[103].mnCode, io_rState ); |
| i_nSpan -= pTable[103].mnEncodedPixels; |
| } |
| // write multiples of 64 pixels up to 2560 |
| if( i_nSpan > 63 ) |
| { |
| sal_uInt32 nTabIndex = 63 + (i_nSpan >> 6); |
| OSL_ASSERT( pTable[nTabIndex].mnEncodedPixels == static_cast<sal_uInt32>(64*(i_nSpan >> 6)) ); |
| putG4Bits( pTable[nTabIndex].mnCodeBits, pTable[nTabIndex].mnCode, io_rState ); |
| i_nSpan -= pTable[nTabIndex].mnEncodedPixels; |
| } |
| putG4Bits( pTable[i_nSpan].mnCodeBits, pTable[i_nSpan].mnCode, io_rState ); |
| } |
| |
| void PDFWriterImpl::writeG4Stream( BitmapReadAccess* i_pBitmap ) |
| { |
| long nW = i_pBitmap->Width(); |
| long nH = i_pBitmap->Height(); |
| if( nW <= 0 || nH <= 0 ) |
| return; |
| if( i_pBitmap->GetBitCount() != 1 ) |
| return; |
| |
| BitStreamState aBitState; |
| |
| // the first reference line is virtual and completely empty |
| const Scanline pFirstRefLine = (Scanline)rtl_allocateZeroMemory( nW/8 + 1 ); |
| Scanline pRefLine = pFirstRefLine; |
| for( long nY = 0; nY < nH; nY++ ) |
| { |
| const Scanline pCurLine = i_pBitmap->GetScanline( nY ); |
| long nLineIndex = 0; |
| bool bRunSet = (*pCurLine & 0x80) ? true : false; |
| bool bRefSet = (*pRefLine & 0x80) ? true : false; |
| long nRunIndex1 = bRunSet ? 0 : findBitRun( pCurLine, 0, nW, bRunSet ); |
| long nRefIndex1 = bRefSet ? 0 : findBitRun( pRefLine, 0, nW, bRefSet ); |
| for( ; nLineIndex < nW; ) |
| { |
| long nRefIndex2 = findBitRun( pRefLine, nRefIndex1, nW, isSet( pRefLine, nRefIndex1 ) ); |
| if( nRefIndex2 >= nRunIndex1 ) |
| { |
| long nDiff = nRefIndex1 - nRunIndex1; |
| if( -3 <= nDiff && nDiff <= 3 ) |
| { // vertical coding |
| static const struct |
| { |
| sal_uInt32 mnCodeBits; |
| sal_uInt32 mnCode; |
| } VerticalCodes[7] = { |
| { 7, 0x03 }, // 0000 011 |
| { 6, 0x03 }, // 0000 11 |
| { 3, 0x03 }, // 011 |
| { 1, 0x1 }, // 1 |
| { 3, 0x2 }, // 010 |
| { 6, 0x02 }, // 0000 10 |
| { 7, 0x02 } // 0000 010 |
| }; |
| // convert to index |
| nDiff += 3; |
| |
| // emit diff code |
| putG4Bits( VerticalCodes[nDiff].mnCodeBits, VerticalCodes[nDiff].mnCode, aBitState ); |
| nLineIndex = nRunIndex1; |
| } |
| else |
| { // difference too large, horizontal coding |
| // emit horz code 001 |
| putG4Bits( 3, 0x1, aBitState ); |
| long nRunIndex2 = findBitRun( pCurLine, nRunIndex1, nW, isSet( pCurLine, nRunIndex1 ) ); |
| bool bWhiteFirst = ( nLineIndex + nRunIndex1 == 0 || ! isSet( pCurLine, nLineIndex ) ); |
| putG4Span( nRunIndex1 - nLineIndex, bWhiteFirst, aBitState ); |
| putG4Span( nRunIndex2 - nRunIndex1, ! bWhiteFirst, aBitState ); |
| nLineIndex = nRunIndex2; |
| } |
| } |
| else |
| { // emit pass code 0001 |
| putG4Bits( 4, 0x1, aBitState ); |
| nLineIndex = nRefIndex2; |
| } |
| if( nLineIndex < nW ) |
| { |
| bool bSet = isSet( pCurLine, nLineIndex ); |
| nRunIndex1 = findBitRun( pCurLine, nLineIndex, nW, bSet ); |
| nRefIndex1 = findBitRun( pRefLine, nLineIndex, nW, ! bSet ); |
| nRefIndex1 = findBitRun( pRefLine, nRefIndex1, nW, bSet ); |
| } |
| } |
| |
| // the current line is the reference for the next line |
| pRefLine = pCurLine; |
| } |
| // terminate strip with EOFB |
| putG4Bits( 12, 1, aBitState ); |
| putG4Bits( 12, 1, aBitState ); |
| if( aBitState.mnNextBitPos != 8 ) |
| { |
| writeBuffer( aBitState.getByte(), 1 ); |
| aBitState.flush(); |
| } |
| |
| rtl_freeMemory( pFirstRefLine ); |
| } |