| /************************************************************** |
| * |
| * 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. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_vcl.hxx" |
| |
| #define _USE_MATH_DEFINES |
| #include <math.h> |
| #include <algorithm> |
| |
| #include <tools/urlobj.hxx> |
| |
| #include <pdfwriter_impl.hxx> |
| |
| #include <basegfx/polygon/b2dpolygon.hxx> |
| #include <basegfx/polygon/b2dpolypolygon.hxx> |
| #include <basegfx/polygon/b2dpolygontools.hxx> |
| #include <basegfx/polygon/b2dpolypolygontools.hxx> |
| #include <basegfx/polygon/b2dpolypolygoncutter.hxx> |
| #include <basegfx/matrix/b2dhommatrix.hxx> |
| |
| #include <osl/thread.h> |
| #include <osl/file.h> |
| |
| #include <rtl/crc.h> |
| #include <rtl/digest.h> |
| #include <rtl/ustrbuf.hxx> |
| |
| #include <tools/debug.hxx> |
| #include <tools/zcodec.hxx> |
| #include <tools/stream.hxx> |
| |
| #include <i18npool/mslangid.hxx> |
| |
| #include <vcl/virdev.hxx> |
| #include <vcl/bmpacc.hxx> |
| #include <vcl/bitmapex.hxx> |
| #include <vcl/image.hxx> |
| #include <vcl/metric.hxx> |
| #include <vcl/svapp.hxx> |
| #include <vcl/lineinfo.hxx> |
| #include "vcl/cvtgrf.hxx" |
| #include "vcl/strhelper.hxx" |
| |
| #include <fontsubset.hxx> |
| #include <outdev.h> |
| #include <sallayout.hxx> |
| #include <textlayout.hxx> |
| #include <salgdi.hxx> |
| |
| #include <icc/sRGB-IEC61966-2.1.hxx> |
| |
| #include <comphelper/processfactory.hxx> |
| |
| #include <com/sun/star/lang/XMultiServiceFactory.hpp> |
| #include <com/sun/star/util/URL.hpp> |
| |
| #include "cppuhelper/implbase1.hxx" |
| |
| using namespace vcl; |
| using namespace rtl; |
| |
| #if (OSL_DEBUG_LEVEL < 2) |
| #define COMPRESS_PAGES |
| #else |
| #define DEBUG_DISABLE_PDFCOMPRESSION // also do not compress streams |
| #endif |
| |
| #ifdef DO_TEST_PDF |
| class PDFTestOutputStream : public PDFOutputStream |
| { |
| public: |
| virtual ~PDFTestOutputStream(); |
| virtual void write( const com::sun::star::uno::Reference< com::sun::star::io::XOutputStream >& xStream ); |
| }; |
| |
| PDFTestOutputStream::~PDFTestOutputStream() |
| { |
| } |
| |
| void PDFTestOutputStream::write( const com::sun::star::uno::Reference< com::sun::star::io::XOutputStream >& xStream ) |
| { |
| OString aStr( "lalala\ntest\ntest\ntest" ); |
| com::sun::star::uno::Sequence< sal_Int8 > aData( aStr.getLength() ); |
| rtl_copyMemory( aData.getArray(), aStr.getStr(), aStr.getLength() ); |
| xStream->writeBytes( aData ); |
| } |
| |
| // this test code cannot be used to test PDF/A-1 because it forces |
| // control item (widgets) to bypass the structure controlling |
| // the embedding of such elements in actual run |
| void doTestCode() |
| { |
| static const char* pHome = getenv( "HOME" ); |
| rtl::OUString aTestFile( RTL_CONSTASCII_USTRINGPARAM( "file://" ) ); |
| aTestFile += rtl::OUString( pHome, strlen( pHome ), RTL_TEXTENCODING_MS_1252 ); |
| aTestFile += rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "/pdf_export_test.pdf" ) ); |
| |
| PDFWriter::PDFWriterContext aContext; |
| aContext.URL = aTestFile; |
| aContext.Version = PDFWriter::PDF_1_4; |
| aContext.Tagged = true; |
| aContext.InitialPage = 2; |
| aContext.DocumentInfo.Title = OUString( RTL_CONSTASCII_USTRINGPARAM( "PDF export test document" ) ); |
| aContext.DocumentInfo.Producer = OUString( RTL_CONSTASCII_USTRINGPARAM( "VCL" ) ); |
| |
| PDFWriter aWriter( aContext ); |
| aWriter.NewPage( 595, 842 ); |
| aWriter.BeginStructureElement( PDFWriter::Document ); |
| // set duration of 3 sec for first page |
| aWriter.SetAutoAdvanceTime( 3 ); |
| aWriter.SetMapMode( MapMode( MAP_100TH_MM ) ); |
| |
| aWriter.SetFillColor( Color( COL_LIGHTRED ) ); |
| aWriter.SetLineColor( Color( COL_LIGHTGREEN ) ); |
| aWriter.DrawRect( Rectangle( Point( 2000, 200 ), Size( 8000, 3000 ) ), 5000, 2000 ); |
| |
| aWriter.SetFont( Font( String( RTL_CONSTASCII_USTRINGPARAM( "Times" ) ), Size( 0, 500 ) ) ); |
| aWriter.SetTextColor( Color( COL_BLACK ) ); |
| aWriter.SetLineColor( Color( COL_BLACK ) ); |
| aWriter.SetFillColor( Color( COL_LIGHTBLUE ) ); |
| |
| Rectangle aRect( Point( 5000, 5000 ), Size( 6000, 3000 ) ); |
| aWriter.DrawRect( aRect ); |
| aWriter.DrawText( aRect, String( RTL_CONSTASCII_USTRINGPARAM( "Link annot 1" ) ) ); |
| sal_Int32 nFirstLink = aWriter.CreateLink( aRect ); |
| PDFNote aNote; |
| aNote.Title = String( RTL_CONSTASCII_USTRINGPARAM( "A small test note" ) ); |
| aNote.Contents = String( RTL_CONSTASCII_USTRINGPARAM( "There is no business like show business like no business i know. Everything about it is appealing." ) ); |
| aWriter.CreateNote( Rectangle( Point( aRect.Right(), aRect.Top() ), Size( 6000, 3000 ) ), aNote ); |
| |
| Rectangle aTargetRect( Point( 3000, 23000 ), Size( 12000, 6000 ) ); |
| aWriter.SetFillColor( Color( COL_LIGHTGREEN ) ); |
| aWriter.DrawRect( aTargetRect ); |
| aWriter.DrawText( aTargetRect, String( RTL_CONSTASCII_USTRINGPARAM( "Dest second link" ) ) ); |
| sal_Int32 nSecondDest = aWriter.CreateDest( aTargetRect ); |
| |
| aWriter.BeginStructureElement( PDFWriter::Section ); |
| aWriter.BeginStructureElement( PDFWriter::Heading ); |
| aWriter.DrawText( Point(4500, 9000), String( RTL_CONSTASCII_USTRINGPARAM( "A small structure test" ) ) ); |
| aWriter.EndStructureElement(); |
| aWriter.BeginStructureElement( PDFWriter::Paragraph ); |
| aWriter.SetStructureAttribute( PDFWriter::WritingMode, PDFWriter::LrTb ); |
| aWriter.SetStructureAttribute( PDFWriter::TextDecorationType, PDFWriter::Underline ); |
| aWriter.DrawText( Rectangle( Point( 4500, 10000 ), Size( 12000, 6000 ) ), |
| String( RTL_CONSTASCII_USTRINGPARAM( "It was the best of PDF, it was the worst of PDF ... or so. This is a pretty nonsensical text to denote a paragraph. I suggest you stop reading it. Because if you read on you might get bored. So continue on your on risk. Hey, you're still here ? Why do you continue to read this as it is of no use at all ? OK, it's your time, but still... . Woah, i even get bored writing this, so let's end this here and now." ) ), |
| TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK |
| ); |
| aWriter.SetActualText( String( RTL_CONSTASCII_USTRINGPARAM( "It was the best of PDF, it was the worst of PDF ... or so. This is a pretty nonsensical text to denote a paragraph. I suggest you stop reading it. Because if you read on you might get bored. So continue on your on risk. Hey, you're still here ? Why do you continue to read this as it is of no use at all ? OK, it's your time, but still... . Woah, i even get bored writing this, so let's end this here and now." ) ) ); |
| aWriter.SetAlternateText( String( RTL_CONSTASCII_USTRINGPARAM( "This paragraph contains some lengthy nonsense to test structural element emission of PDFWriter." ) ) ); |
| aWriter.EndStructureElement(); |
| sal_Int32 nLongPara = aWriter.BeginStructureElement( PDFWriter::Paragraph ); |
| aWriter.SetStructureAttribute( PDFWriter::WritingMode, PDFWriter::LrTb ); |
| aWriter.DrawText( Rectangle( Point( 4500, 19000 ), Size( 12000, 1000 ) ), |
| String( RTL_CONSTASCII_USTRINGPARAM( "This paragraph is nothing special either but ends on the next page structurewise" ) ), |
| TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK |
| ); |
| |
| aWriter.NewPage( 595, 842 ); |
| // test AddStream interface |
| aWriter.AddStream( String( RTL_CONSTASCII_USTRINGPARAM( "text/plain" ) ), new PDFTestOutputStream(), true ); |
| // set transitional mode |
| aWriter.SetPageTransition( PDFWriter::WipeRightToLeft, 1500 ); |
| aWriter.SetMapMode( MapMode( MAP_100TH_MM ) ); |
| aWriter.SetTextColor( Color( COL_BLACK ) ); |
| aWriter.SetFont( Font( String( RTL_CONSTASCII_USTRINGPARAM( "Times" ) ), Size( 0, 500 ) ) ); |
| aWriter.DrawText( Rectangle( Point( 4500, 1500 ), Size( 12000, 3000 ) ), |
| String( RTL_CONSTASCII_USTRINGPARAM( "Here's where all things come to an end ... well at least the paragaph from the last page." ) ), |
| TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK |
| ); |
| aWriter.EndStructureElement(); |
| |
| aWriter.SetFillColor( Color( COL_LIGHTBLUE ) ); |
| // disable structure |
| aWriter.BeginStructureElement( PDFWriter::NonStructElement ); |
| aWriter.DrawRect( aRect ); |
| aWriter.BeginStructureElement( PDFWriter::Paragraph ); |
| aWriter.DrawText( aRect, String( RTL_CONSTASCII_USTRINGPARAM( "Link annot 2" ) ) ); |
| sal_Int32 nSecondLink = aWriter.CreateLink( aRect ); |
| |
| aWriter.SetFillColor( Color( COL_LIGHTGREEN ) ); |
| aWriter.BeginStructureElement( PDFWriter::ListItem ); |
| aWriter.DrawRect( aTargetRect ); |
| aWriter.DrawText( aTargetRect, String( RTL_CONSTASCII_USTRINGPARAM( "Dest first link" ) ) ); |
| sal_Int32 nFirstDest = aWriter.CreateDest( aTargetRect ); |
| // enable structure |
| aWriter.EndStructureElement(); |
| // add something to the long paragraph as an afterthought |
| sal_Int32 nSaveStruct = aWriter.GetCurrentStructureElement(); |
| aWriter.SetCurrentStructureElement( nLongPara ); |
| aWriter.DrawText( Rectangle( Point( 4500,4500 ), Size( 12000, 1000 ) ), |
| String( RTL_CONSTASCII_USTRINGPARAM( "Add something to the longish paragraph above." ) ), |
| TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK ); |
| aWriter.SetCurrentStructureElement( nSaveStruct ); |
| aWriter.EndStructureElement(); |
| aWriter.EndStructureElement(); |
| aWriter.BeginStructureElement( PDFWriter::Figure ); |
| aWriter.BeginStructureElement( PDFWriter::Caption ); |
| aWriter.DrawText( Point( 4500, 9000 ), String( RTL_CONSTASCII_USTRINGPARAM( "Some drawing stuff inside the structure" ) ) ); |
| aWriter.EndStructureElement(); |
| |
| // test clipping |
| basegfx::B2DPolyPolygon aClip; |
| basegfx::B2DPolygon aClipPoly; |
| aClipPoly.append( basegfx::B2DPoint( 8250, 9600 ) ); |
| aClipPoly.append( basegfx::B2DPoint( 16500, 11100 ) ); |
| aClipPoly.append( basegfx::B2DPoint( 8250, 12600 ) ); |
| aClipPoly.append( basegfx::B2DPoint( 4500, 11100 ) ); |
| aClipPoly.setClosed( true ); |
| //aClipPoly.flip(); |
| aClip.append( aClipPoly ); |
| |
| aWriter.Push( PUSH_CLIPREGION | PUSH_FILLCOLOR ); |
| aWriter.SetClipRegion( aClip ); |
| aWriter.DrawEllipse( Rectangle( Point( 4500, 9600 ), Size( 12000, 3000 ) ) ); |
| aWriter.MoveClipRegion( 1000, 500 ); |
| aWriter.SetFillColor( Color( COL_RED ) ); |
| aWriter.DrawEllipse( Rectangle( Point( 4500, 9600 ), Size( 12000, 3000 ) ) ); |
| aWriter.Pop(); |
| // test transparency |
| // draw background |
| Rectangle aTranspRect( Point( 7500, 13500 ), Size( 9000, 6000 ) ); |
| aWriter.SetFillColor( Color( COL_LIGHTRED ) ); |
| aWriter.DrawRect( aTranspRect ); |
| aWriter.BeginTransparencyGroup(); |
| |
| aWriter.SetFillColor( Color( COL_LIGHTGREEN ) ); |
| aWriter.DrawEllipse( aTranspRect ); |
| aWriter.SetTextColor( Color( COL_LIGHTBLUE ) ); |
| aWriter.DrawText( aTranspRect, |
| String( RTL_CONSTASCII_USTRINGPARAM( "Some transparent text" ) ), |
| TEXT_DRAW_CENTER | TEXT_DRAW_VCENTER | TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK ); |
| |
| aWriter.EndTransparencyGroup( aTranspRect, 50 ); |
| |
| // prepare an alpha mask |
| Bitmap aTransMask( Size( 256, 256 ), 8, &Bitmap::GetGreyPalette( 256 ) ); |
| BitmapWriteAccess* pAcc = aTransMask.AcquireWriteAccess(); |
| for( int nX = 0; nX < 256; nX++ ) |
| for( int nY = 0; nY < 256; nY++ ) |
| pAcc->SetPixel( nX, nY, BitmapColor( (sal_uInt8)((nX+nY)/2) ) ); |
| aTransMask.ReleaseAccess( pAcc ); |
| aTransMask.SetPrefMapMode( MAP_MM ); |
| aTransMask.SetPrefSize( Size( 10, 10 ) ); |
| |
| aWriter.DrawBitmap( Point( 600, 13500 ), Size( 3000, 3000 ), aTransMask ); |
| |
| aTranspRect = Rectangle( Point( 4200, 13500 ), Size( 3000, 3000 ) ); |
| aWriter.SetFillColor( Color( COL_LIGHTRED ) ); |
| aWriter.DrawRect( aTranspRect ); |
| aWriter.SetFillColor( Color( COL_LIGHTGREEN ) ); |
| aWriter.DrawEllipse( aTranspRect ); |
| aWriter.SetTextColor( Color( COL_LIGHTBLUE ) ); |
| aWriter.DrawText( aTranspRect, |
| String( RTL_CONSTASCII_USTRINGPARAM( "Some transparent text" ) ), |
| TEXT_DRAW_CENTER | TEXT_DRAW_VCENTER | TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK ); |
| aTranspRect = Rectangle( Point( 1500, 16500 ), Size( 4800, 3000 ) ); |
| aWriter.SetFillColor( Color( COL_LIGHTRED ) ); |
| aWriter.DrawRect( aTranspRect ); |
| aWriter.BeginTransparencyGroup(); |
| aWriter.SetFillColor( Color( COL_LIGHTGREEN ) ); |
| aWriter.DrawEllipse( aTranspRect ); |
| aWriter.SetTextColor( Color( COL_LIGHTBLUE ) ); |
| aWriter.DrawText( aTranspRect, |
| String( RTL_CONSTASCII_USTRINGPARAM( "Some transparent text" ) ), |
| TEXT_DRAW_CENTER | TEXT_DRAW_VCENTER | TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK ); |
| aWriter.EndTransparencyGroup( aTranspRect, aTransMask ); |
| |
| Bitmap aImageBmp( Size( 256, 256 ), 24 ); |
| pAcc = aImageBmp.AcquireWriteAccess(); |
| pAcc->SetFillColor( Color( 0xff, 0, 0xff ) ); |
| pAcc->FillRect( Rectangle( Point( 0, 0 ), Size( 256, 256 ) ) ); |
| aImageBmp.ReleaseAccess( pAcc ); |
| BitmapEx aBmpEx( aImageBmp, AlphaMask( aTransMask ) ); |
| aWriter.DrawBitmapEx( Point( 1500, 19500 ), Size( 4800, 3000 ), aBmpEx ); |
| |
| |
| aWriter.EndStructureElement(); |
| aWriter.EndStructureElement(); |
| |
| LineInfo aLI( LINE_DASH, 3 ); |
| aLI.SetDashCount( 2 ); |
| aLI.SetDashLen( 50 ); |
| aLI.SetDotCount( 2 ); |
| aLI.SetDotLen( 25 ); |
| aLI.SetDistance( 15 ); |
| Point aLIPoints[] = { Point( 4000, 10000 ), |
| Point( 8000, 12000 ), |
| Point( 3000, 19000 ) }; |
| Polygon aLIPoly( 3, aLIPoints ); |
| aWriter.SetLineColor( Color( COL_BLUE ) ); |
| aWriter.SetFillColor(); |
| aWriter.DrawPolyLine( aLIPoly, aLI ); |
| |
| aLI.SetDashCount( 4 ); |
| aLIPoly.Move( 1000, 1000 ); |
| aWriter.DrawPolyLine( aLIPoly, aLI ); |
| |
| aWriter.NewPage( 595, 842 ); |
| aWriter.SetMapMode( MapMode( MAP_100TH_MM ) ); |
| Wallpaper aWall( aTransMask ); |
| aWall.SetStyle( WALLPAPER_TILE ); |
| aWriter.DrawWallpaper( Rectangle( Point( 4400, 4200 ), Size( 10200, 6300 ) ), aWall ); |
| |
| aWriter.Push( PUSH_ALL ); |
| aWriter.BeginPattern(Rectangle(Point(0,0),Size(2000,1000))); |
| aWriter.SetFillColor( Color( COL_RED ) ); |
| aWriter.SetLineColor( Color( COL_LIGHTBLUE ) ); |
| Point aFillPoints[] = { Point( 1000, 0 ), |
| Point( 0, 1000 ), |
| Point( 2000, 1000 ) }; |
| aWriter.DrawPolygon( Polygon( 3, aFillPoints ) ); |
| aWriter.DrawBitmap( Point( 200, 200 ), Size( 1600, 600 ), aTransMask ); |
| aWriter.DrawText( Rectangle( Point( 200, 200 ), Size( 1600, 600 ) ), String( RTL_CONSTASCII_USTRINGPARAM( "Pattern" ) ) ); |
| sal_Int32 nPattern = aWriter.EndPattern( SvtGraphicFill::Transform() ); |
| aWriter.Pop(); |
| Rectangle aPolyRect( Point( 3800, 11200 ), Size( 10200, 6300 ) ); |
| aWriter.DrawPolyPolygon( PolyPolygon( Polygon( aPolyRect ) ), nPattern, true ); |
| aWriter.SetFillColor(); |
| aWriter.SetLineColor( Color( COL_LIGHTBLUE ) ); |
| aWriter.DrawRect( aPolyRect ); |
| |
| aWriter.NewPage( 595, 842 ); |
| aWriter.SetMapMode( MapMode( MAP_100TH_MM ) ); |
| aWriter.SetFont( Font( String( RTL_CONSTASCII_USTRINGPARAM( "Times" ) ), Size( 0, 500 ) ) ); |
| aWriter.SetTextColor( Color( COL_BLACK ) ); |
| aRect = Rectangle( Point( 4500, 6000 ), Size( 6000, 1500 ) ); |
| aWriter.DrawRect( aRect ); |
| aWriter.DrawText( aRect, String( RTL_CONSTASCII_USTRINGPARAM( "www.heise.de" ) ) ); |
| sal_Int32 nURILink = aWriter.CreateLink( aRect ); |
| aWriter.SetLinkURL( nURILink, OUString( RTL_CONSTASCII_USTRINGPARAM( "http://www.heise.de" ) ) ); |
| |
| aWriter.SetLinkDest( nFirstLink, nFirstDest ); |
| aWriter.SetLinkDest( nSecondLink, nSecondDest ); |
| |
| // include a button |
| PDFWriter::PushButtonWidget aBtn; |
| aBtn.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "testButton" ) ); |
| aBtn.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "A test button" ) ); |
| aBtn.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "hit me" ) ); |
| aBtn.Location = Rectangle( Point( 4500, 9000 ), Size( 4500, 3000 ) ); |
| aBtn.Border = aBtn.Background = true; |
| aWriter.CreateControl( aBtn ); |
| |
| // include a uri button |
| PDFWriter::PushButtonWidget aUriBtn; |
| aUriBtn.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "wwwButton" ) ); |
| aUriBtn.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "A URI button" ) ); |
| aUriBtn.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "to www" ) ); |
| aUriBtn.Location = Rectangle( Point( 9500, 9000 ), Size( 4500, 3000 ) ); |
| aUriBtn.Border = aUriBtn.Background = true; |
| aUriBtn.URL = OUString( RTL_CONSTASCII_USTRINGPARAM( "http://www.heise.de" ) ); |
| aWriter.CreateControl( aUriBtn ); |
| |
| // include a dest button |
| PDFWriter::PushButtonWidget aDstBtn; |
| aDstBtn.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "destButton" ) ); |
| aDstBtn.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "A Dest button" ) ); |
| aDstBtn.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "to paragraph" ) ); |
| aDstBtn.Location = Rectangle( Point( 14500, 9000 ), Size( 4500, 3000 ) ); |
| aDstBtn.Border = aDstBtn.Background = true; |
| aDstBtn.Dest = nFirstDest; |
| aWriter.CreateControl( aDstBtn ); |
| |
| PDFWriter::CheckBoxWidget aCBox; |
| aCBox.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "textCheckBox" ) ); |
| aCBox.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "A test check box" ) ); |
| aCBox.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "check me" ) ); |
| aCBox.Location = Rectangle( Point( 4500, 13500 ), Size( 3000, 750 ) ); |
| aCBox.Checked = true; |
| aCBox.Border = aCBox.Background = false; |
| aWriter.CreateControl( aCBox ); |
| |
| PDFWriter::CheckBoxWidget aCBox2; |
| aCBox2.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "textCheckBox2" ) ); |
| aCBox2.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "Another test check box" ) ); |
| aCBox2.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "check me right" ) ); |
| aCBox2.Location = Rectangle( Point( 4500, 14250 ), Size( 3000, 750 ) ); |
| aCBox2.Checked = true; |
| aCBox2.Border = aCBox2.Background = false; |
| aCBox2.ButtonIsLeft = false; |
| aWriter.CreateControl( aCBox2 ); |
| |
| PDFWriter::RadioButtonWidget aRB1; |
| aRB1.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "rb1_1" ) ); |
| aRB1.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "radio 1 button 1" ) ); |
| aRB1.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "Despair" ) ); |
| aRB1.Location = Rectangle( Point( 4500, 15000 ), Size( 6000, 1000 ) ); |
| aRB1.Selected = true; |
| aRB1.RadioGroup = 1; |
| aRB1.Border = aRB1.Background = true; |
| aRB1.ButtonIsLeft = false; |
| aRB1.BorderColor = Color( COL_LIGHTGREEN ); |
| aRB1.BackgroundColor = Color( COL_LIGHTBLUE ); |
| aRB1.TextColor = Color( COL_LIGHTRED ); |
| aRB1.TextFont = Font( String( RTL_CONSTASCII_USTRINGPARAM( "Courier" ) ), Size( 0, 800 ) ); |
| aWriter.CreateControl( aRB1 ); |
| |
| PDFWriter::RadioButtonWidget aRB2; |
| aRB2.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "rb2_1" ) ); |
| aRB2.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "radio 2 button 1" ) ); |
| aRB2.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "Joy" ) ); |
| aRB2.Location = Rectangle( Point( 10500, 15000 ), Size( 3000, 1000 ) ); |
| aRB2.Selected = true; |
| aRB2.RadioGroup = 2; |
| aWriter.CreateControl( aRB2 ); |
| |
| PDFWriter::RadioButtonWidget aRB3; |
| aRB3.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "rb1_2" ) ); |
| aRB3.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "radio 1 button 2" ) ); |
| aRB3.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "Desperation" ) ); |
| aRB3.Location = Rectangle( Point( 4500, 16000 ), Size( 3000, 1000 ) ); |
| aRB3.Selected = true; |
| aRB3.RadioGroup = 1; |
| aWriter.CreateControl( aRB3 ); |
| |
| PDFWriter::EditWidget aEditBox; |
| aEditBox.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "testEdit" ) ); |
| aEditBox.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "A test edit field" ) ); |
| aEditBox.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "A little test text" ) ); |
| aEditBox.TextStyle = TEXT_DRAW_LEFT | TEXT_DRAW_VCENTER; |
| aEditBox.Location = Rectangle( Point( 10000, 18000 ), Size( 5000, 1500 ) ); |
| aEditBox.MaxLen = 100; |
| aEditBox.Border = aEditBox.Background = true; |
| aEditBox.BorderColor = Color( COL_BLACK ); |
| aWriter.CreateControl( aEditBox ); |
| |
| // normal list box |
| PDFWriter::ListBoxWidget aLstBox; |
| aLstBox.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "testListBox" ) ); |
| aLstBox.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "One" ) ); |
| aLstBox.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "select me" ) ); |
| aLstBox.Location = Rectangle( Point( 4500, 18000 ), Size( 3000, 1500 ) ); |
| aLstBox.Sort = true; |
| aLstBox.MultiSelect = true; |
| aLstBox.Border = aLstBox.Background = true; |
| aLstBox.BorderColor = Color( COL_BLACK ); |
| aLstBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "One" ) ) ); |
| aLstBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "Two" ) ) ); |
| aLstBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "Three" ) ) ); |
| aLstBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "Four" ) ) ); |
| aLstBox.SelectedEntries.push_back( 1 ); |
| aLstBox.SelectedEntries.push_back( 2 ); |
| aWriter.CreateControl( aLstBox ); |
| |
| // dropdown list box |
| aLstBox.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "testDropDownListBox" ) ); |
| aLstBox.DropDown = true; |
| aLstBox.Location = Rectangle( Point( 4500, 19500 ), Size( 3000, 500 ) ); |
| aWriter.CreateControl( aLstBox ); |
| |
| // combo box |
| PDFWriter::ComboBoxWidget aComboBox; |
| aComboBox.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "testComboBox" ) ); |
| aComboBox.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "test a combobox" ) ); |
| aComboBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "Larry" ) ) ); |
| aComboBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "Curly" ) ) ); |
| aComboBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "Moe" ) ) ); |
| aComboBox.Location = Rectangle( Point( 4500, 20000 ), Size( 3000, 500 ) ); |
| aWriter.CreateControl( aComboBox ); |
| |
| // test outlines |
| sal_Int32 nPage1OL = aWriter.CreateOutlineItem(); |
| aWriter.SetOutlineItemText( nPage1OL, OUString( RTL_CONSTASCII_USTRINGPARAM( "Page 1" ) ) ); |
| aWriter.SetOutlineItemDest( nPage1OL, nSecondDest ); |
| aWriter.CreateOutlineItem( nPage1OL, OUString( RTL_CONSTASCII_USTRINGPARAM( "Dest 2" ) ), nSecondDest ); |
| aWriter.CreateOutlineItem( nPage1OL, OUString( RTL_CONSTASCII_USTRINGPARAM( "Dest 2 revisited" ) ), nSecondDest ); |
| aWriter.CreateOutlineItem( nPage1OL, OUString( RTL_CONSTASCII_USTRINGPARAM( "Dest 2 again" ) ), nSecondDest ); |
| sal_Int32 nPage2OL = aWriter.CreateOutlineItem(); |
| aWriter.SetOutlineItemText( nPage2OL, OUString( RTL_CONSTASCII_USTRINGPARAM( "Page 2" ) ) ); |
| aWriter.CreateOutlineItem( nPage2OL, OUString( RTL_CONSTASCII_USTRINGPARAM( "Dest 1" ) ), nFirstDest ); |
| |
| aWriter.EndStructureElement(); // close document |
| aWriter.Emit(); |
| } |
| #endif |
| |
| static const sal_Int32 nLog10Divisor = 1; |
| static const double fDivisor = 10.0; |
| |
| static inline double pixelToPoint( sal_Int32 px ) { return double(px)/fDivisor; } |
| static inline double pixelToPoint( double px ) { return px/fDivisor; } |
| static inline sal_Int32 pointToPixel( double pt ) { return sal_Int32(pt*fDivisor); } |
| |
| const sal_uInt8 PDFWriterImpl::s_nPadString[32] = |
| { |
| 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08, |
| 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A |
| }; |
| |
| static void appendHex( sal_Int8 nInt, OStringBuffer& rBuffer ) |
| { |
| static const sal_Char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', |
| '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; |
| rBuffer.append( pHexDigits[ (nInt >> 4) & 15 ] ); |
| rBuffer.append( pHexDigits[ nInt & 15 ] ); |
| } |
| |
| static void appendName( const OUString& rStr, OStringBuffer& rBuffer ) |
| { |
| // FIXME i59651 add a check for max length of 127 chars? Per PDF spec 1.4, appendix C.1 |
| // I guess than when reading the #xx sequence it will count for a single character. |
| OString aStr( OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 ) ); |
| const sal_Char* pStr = aStr.getStr(); |
| int nLen = aStr.getLength(); |
| for( int i = 0; i < nLen; i++ ) |
| { |
| /* #i16920# PDF recommendation: output UTF8, any byte |
| * outside the interval [33(=ASCII'!');126(=ASCII'~')] |
| * should be escaped hexadecimal |
| * for the sake of ghostscript which also reads PDF |
| * but has a narrower acceptance rate we only pass |
| * alphanumerics and '-' literally. |
| */ |
| if( (pStr[i] >= 'A' && pStr[i] <= 'Z' ) || |
| (pStr[i] >= 'a' && pStr[i] <= 'z' ) || |
| (pStr[i] >= '0' && pStr[i] <= '9' ) || |
| pStr[i] == '-' ) |
| { |
| rBuffer.append( pStr[i] ); |
| } |
| else |
| { |
| rBuffer.append( '#' ); |
| appendHex( (sal_Int8)pStr[i], rBuffer ); |
| } |
| } |
| } |
| |
| static void appendName( const sal_Char* pStr, OStringBuffer& rBuffer ) |
| { |
| //FIXME i59651 see above |
| while( pStr && *pStr ) |
| { |
| if( (*pStr >= 'A' && *pStr <= 'Z' ) || |
| (*pStr >= 'a' && *pStr <= 'z' ) || |
| (*pStr >= '0' && *pStr <= '9' ) || |
| *pStr == '-' ) |
| { |
| rBuffer.append( *pStr ); |
| } |
| else |
| { |
| rBuffer.append( '#' ); |
| appendHex( (sal_Int8)*pStr, rBuffer ); |
| } |
| pStr++; |
| } |
| } |
| |
| //used only to emit encoded passwords |
| static void appendLiteralString( const sal_Char* pStr, sal_Int32 nLength, OStringBuffer& rBuffer ) |
| { |
| while( nLength ) |
| { |
| switch( *pStr ) |
| { |
| case '\n' : |
| rBuffer.append( "\\n" ); |
| break; |
| case '\r' : |
| rBuffer.append( "\\r" ); |
| break; |
| case '\t' : |
| rBuffer.append( "\\t" ); |
| break; |
| case '\b' : |
| rBuffer.append( "\\b" ); |
| break; |
| case '\f' : |
| rBuffer.append( "\\f" ); |
| break; |
| case '(' : |
| case ')' : |
| case '\\' : |
| rBuffer.append( "\\" ); |
| rBuffer.append( (sal_Char) *pStr ); |
| break; |
| default: |
| rBuffer.append( (sal_Char) *pStr ); |
| break; |
| } |
| pStr++; |
| nLength--; |
| } |
| } |
| |
| /**--->i56629 |
| * Convert a string before using it. |
| * |
| * This string conversion function is needed because the destination name |
| * in a PDF file seen through an Internet browser should be |
| * specially crafted, in order to be used directly by the browser. |
| * In this way the fragment part of a hyperlink to a PDF file (e.g. something |
| * as 'test1/test2/a-file.pdf#thefragment) will be (hopefully) interpreted by the |
| * PDF reader (currently only Adobe Reader plug-in seems to be working that way) called |
| * from inside the Internet browser as: 'open the file test1/test2/a-file.pdf |
| * and go to named destination thefragment using default zoom'. |
| * The conversion is needed because in case of a fragment in the form: Slide%201 |
| * (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201 |
| * using this conversion, in both the generated named destinations, fragment and GoToR |
| * destination. |
| * |
| * The names for destinations are name objects and so they don't need to be encrypted |
| * even though they expose the content of PDF file (e.g. guessing the PDF content from the |
| * destination name). |
| * |
| * Fhurter limitation: it is advisable to use standard ASCII characters for |
| * OOo bookmarks. |
| */ |
| static void appendDestinationName( const rtl::OUString& rString, OStringBuffer& rBuffer ) |
| { |
| const sal_Unicode* pStr = rString.getStr(); |
| sal_Int32 nLen = rString.getLength(); |
| for( int i = 0; i < nLen; i++ ) |
| { |
| sal_Unicode aChar = pStr[i]; |
| if( (aChar >= '0' && aChar <= '9' ) || |
| (aChar >= 'a' && aChar <= 'z' ) || |
| (aChar >= 'A' && aChar <= 'Z' ) || |
| aChar == '-' ) |
| { |
| rBuffer.append((sal_Char)aChar); |
| } |
| else |
| { |
| sal_Int8 aValueHigh = sal_Int8(aChar >> 8); |
| if(aValueHigh > 0) |
| appendHex( aValueHigh, rBuffer ); |
| appendHex( (sal_Int8)(aChar & 255 ), rBuffer ); |
| } |
| } |
| } |
| //<--- i56629 |
| |
| static void appendUnicodeTextString( const rtl::OUString& rString, OStringBuffer& rBuffer ) |
| { |
| rBuffer.append( "FEFF" ); |
| const sal_Unicode* pStr = rString.getStr(); |
| sal_Int32 nLen = rString.getLength(); |
| for( int i = 0; i < nLen; i++ ) |
| { |
| sal_Unicode aChar = pStr[i]; |
| appendHex( (sal_Int8)(aChar >> 8), rBuffer ); |
| appendHex( (sal_Int8)(aChar & 255 ), rBuffer ); |
| } |
| } |
| |
| void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl ) |
| { |
| /* #i80258# previously we use appendName here |
| however we need a slightly different coding scheme than the normal |
| name encoding for field names |
| */ |
| const OUString& rName = (m_aContext.Version > PDFWriter::PDF_1_2) ? i_rControl.Name : i_rControl.Text; |
| OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) ); |
| const sal_Char* pStr = aStr.getStr(); |
| int nLen = aStr.getLength(); |
| |
| OStringBuffer aBuffer( rName.getLength()+64 ); |
| for( int i = 0; i < nLen; i++ ) |
| { |
| /* #i16920# PDF recommendation: output UTF8, any byte |
| * outside the interval [32(=ASCII' ');126(=ASCII'~')] |
| * should be escaped hexadecimal |
| */ |
| if( (pStr[i] >= 32 && pStr[i] <= 126 ) ) |
| aBuffer.append( pStr[i] ); |
| else |
| { |
| aBuffer.append( '#' ); |
| appendHex( (sal_Int8)pStr[i], aBuffer ); |
| } |
| } |
| |
| OString aFullName( aBuffer.makeStringAndClear() ); |
| |
| /* #i82785# create hierarchical fields down to the for each dot in i_rName */ |
| sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0; |
| OString aPartialName; |
| OString aDomain; |
| do |
| { |
| nLastTokenIndex = nTokenIndex; |
| aPartialName = aFullName.getToken( 0, '.', nTokenIndex ); |
| if( nTokenIndex != -1 ) |
| { |
| // find or create a hierarchical field |
| // first find the fully qualified name up to this field |
| aDomain = aFullName.copy( 0, nTokenIndex-1 ); |
| std::hash_map< rtl::OString, sal_Int32, rtl::OStringHash >::const_iterator it = m_aFieldNameMap.find( aDomain ); |
| if( it == m_aFieldNameMap.end() ) |
| { |
| // create new hierarchy field |
| sal_Int32 nNewWidget = m_aWidgets.size(); |
| m_aWidgets.push_back( PDFWidget() ); |
| m_aWidgets[nNewWidget].m_nObject = createObject(); |
| m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy; |
| m_aWidgets[nNewWidget].m_aName = aPartialName; |
| m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject; |
| m_aFieldNameMap[aDomain] = nNewWidget; |
| m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject; |
| if( nLastTokenIndex > 0 ) |
| { |
| // this field is not a root field and |
| // needs to be inserted to its parent |
| OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) ); |
| it = m_aFieldNameMap.find( aParentDomain ); |
| OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" ); |
| if( it != m_aFieldNameMap.end() ) |
| { |
| OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" ); |
| if( it->second < sal_Int32(m_aWidgets.size()) ) |
| { |
| PDFWidget& rParentField( m_aWidgets[it->second] ); |
| rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject ); |
| rParentField.m_aKidsIndex.push_back( nNewWidget ); |
| m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject; |
| } |
| } |
| } |
| } |
| else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy ) |
| { |
| // this is invalid, someone tries to have a terminal field as parent |
| // example: a button with the name foo.bar exists and |
| // another button is named foo.bar.no |
| // workaround: put the second terminal field as much up in the hierarchy as |
| // necessary to have a non-terminal field as parent (or none at all) |
| // since it->second already is terminal, we just need to use its parent |
| aDomain = OString(); |
| aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 ); |
| if( nLastTokenIndex > 0 ) |
| { |
| aDomain = aFullName.copy( 0, nLastTokenIndex-1 ); |
| OStringBuffer aBuf( aDomain.getLength() + 1 + aPartialName.getLength() ); |
| aBuf.append( aDomain ); |
| aBuf.append( '.' ); |
| aBuf.append( aPartialName ); |
| aFullName = aBuf.makeStringAndClear(); |
| } |
| else |
| aFullName = aPartialName; |
| break; |
| } |
| } |
| } while( nTokenIndex != -1 ); |
| |
| // insert widget into its hierarchy field |
| if( aDomain.getLength() ) |
| { |
| std::hash_map< rtl::OString, sal_Int32, rtl::OStringHash >::const_iterator it = m_aFieldNameMap.find( aDomain ); |
| if( it != m_aFieldNameMap.end() ) |
| { |
| OSL_ENSURE( it->second >= 0 && it->second < sal_Int32( m_aWidgets.size() ), "invalid field index" ); |
| if( it->second >= 0 && it->second < sal_Int32(m_aWidgets.size()) ) |
| { |
| m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject; |
| m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject); |
| m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex ); |
| } |
| } |
| } |
| |
| if( aPartialName.getLength() == 0 ) |
| { |
| // how funny, an empty field name |
| if( i_rControl.getType() == PDFWriter::RadioButton ) |
| { |
| aPartialName = "RadioGroup"; |
| aPartialName += OString::valueOf( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup ); |
| } |
| else |
| aPartialName = OString( "Widget" ); |
| } |
| |
| if( ! m_aContext.AllowDuplicateFieldNames ) |
| { |
| std::hash_map<OString, sal_Int32, OStringHash>::iterator it = m_aFieldNameMap.find( aFullName ); |
| |
| if( it != m_aFieldNameMap.end() ) // not unique |
| { |
| std::hash_map< OString, sal_Int32, OStringHash >::const_iterator check_it; |
| OString aTry; |
| sal_Int32 nTry = 2; |
| do |
| { |
| OStringBuffer aUnique( aFullName.getLength() + 16 ); |
| aUnique.append( aFullName ); |
| aUnique.append( '_' ); |
| aUnique.append( nTry++ ); |
| aTry = aUnique.makeStringAndClear(); |
| check_it = m_aFieldNameMap.find( aTry ); |
| } while( check_it != m_aFieldNameMap.end() ); |
| aFullName = aTry; |
| m_aFieldNameMap[ aFullName ] = i_nWidgetIndex; |
| aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 ); |
| } |
| else |
| m_aFieldNameMap[ aFullName ] = i_nWidgetIndex; |
| } |
| |
| // finally |
| m_aWidgets[i_nWidgetIndex].m_aName = aPartialName; |
| } |
| |
| static void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = nLog10Divisor ) |
| { |
| if( nValue < 0 ) |
| { |
| rBuffer.append( '-' ); |
| nValue = -nValue; |
| } |
| sal_Int32 nFactor = 1, nDiv = nPrecision; |
| while( nDiv-- ) |
| nFactor *= 10; |
| |
| sal_Int32 nInt = nValue / nFactor; |
| rBuffer.append( nInt ); |
| if( nFactor > 1 ) |
| { |
| sal_Int32 nDecimal = nValue % nFactor; |
| if( nDecimal ) |
| { |
| rBuffer.append( '.' ); |
| // omit trailing zeros |
| while( (nDecimal % 10) == 0 ) |
| nDecimal /= 10; |
| rBuffer.append( nDecimal ); |
| } |
| } |
| } |
| |
| |
| // appends a double. PDF does not accept exponential format, only fixed point |
| static void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 5 ) |
| { |
| bool bNeg = false; |
| if( fValue < 0.0 ) |
| { |
| bNeg = true; |
| fValue=-fValue; |
| } |
| |
| sal_Int64 nInt = (sal_Int64)fValue; |
| fValue -= (double)nInt; |
| // optimizing hardware may lead to a value of 1.0 after the subtraction |
| if( fValue == 1.0 || log10( 1.0-fValue ) <= -nPrecision ) |
| { |
| nInt++; |
| fValue = 0.0; |
| } |
| sal_Int64 nFrac = 0; |
| if( fValue ) |
| { |
| fValue *= pow( 10.0, (double)nPrecision ); |
| nFrac = (sal_Int64)fValue; |
| } |
| if( bNeg && ( nInt || nFrac ) ) |
| rBuffer.append( '-' ); |
| rBuffer.append( nInt ); |
| if( nFrac ) |
| { |
| int i; |
| rBuffer.append( '.' ); |
| sal_Int64 nBound = (sal_Int64)(pow( 10.0, nPrecision - 1.0 )+0.5); |
| for ( i = 0; ( i < nPrecision ) && nFrac; i++ ) |
| { |
| sal_Int64 nNumb = nFrac / nBound; |
| nFrac -= nNumb * nBound; |
| rBuffer.append( nNumb ); |
| nBound /= 10; |
| } |
| } |
| } |
| |
| |
| static void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey = false ) |
| { |
| |
| if( rColor != Color( COL_TRANSPARENT ) ) |
| { |
| if( bConvertToGrey ) |
| { |
| sal_uInt8 cByte = rColor.GetLuminance(); |
| appendDouble( (double)cByte / 255.0, rBuffer ); |
| } |
| else |
| { |
| appendDouble( (double)rColor.GetRed() / 255.0, rBuffer ); |
| rBuffer.append( ' ' ); |
| appendDouble( (double)rColor.GetGreen() / 255.0, rBuffer ); |
| rBuffer.append( ' ' ); |
| appendDouble( (double)rColor.GetBlue() / 255.0, rBuffer ); |
| } |
| } |
| } |
| |
| void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer ) |
| { |
| if( rColor != Color( COL_TRANSPARENT ) ) |
| { |
| bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale; |
| appendColor( rColor, rBuffer, bGrey ); |
| rBuffer.append( bGrey ? " G" : " RG" ); |
| } |
| } |
| |
| void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer ) |
| { |
| if( rColor != Color( COL_TRANSPARENT ) ) |
| { |
| bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale; |
| appendColor( rColor, rBuffer, bGrey ); |
| rBuffer.append( bGrey ? " g" : " rg" ); |
| } |
| } |
| |
| // matrix helper class |
| // TODO: use basegfx matrix class instead or derive from it |
| namespace vcl // TODO: use anonymous namespace to keep this class local |
| { |
| /* for sparse matrices of the form (2D linear transformations) |
| * f[0] f[1] 0 |
| * f[2] f[3] 0 |
| * f[4] f[5] 1 |
| */ |
| class Matrix3 |
| { |
| double f[6]; |
| |
| void set( double *pn ) { for( int i = 0 ; i < 6; i++ ) f[i] = pn[i]; } |
| public: |
| Matrix3(); |
| ~Matrix3() {} |
| |
| void skew( double alpha, double beta ); |
| void scale( double sx, double sy ); |
| void rotate( double angle ); |
| void translate( double tx, double ty ); |
| bool invert(); |
| |
| void append( PDFWriterImpl::PDFPage& rPage, OStringBuffer& rBuffer, Point* pBack = NULL ); |
| |
| Point transform( const Point& rPoint ) const; |
| }; |
| } |
| |
| Matrix3::Matrix3() |
| { |
| // initialize to unity |
| f[0] = 1.0; |
| f[1] = 0.0; |
| f[2] = 0.0; |
| f[3] = 1.0; |
| f[4] = 0.0; |
| f[5] = 0.0; |
| } |
| |
| Point Matrix3::transform( const Point& rOrig ) const |
| { |
| double x = (double)rOrig.X(), y = (double)rOrig.Y(); |
| return Point( (int)(x*f[0] + y*f[2] + f[4]), (int)(x*f[1] + y*f[3] + f[5]) ); |
| } |
| |
| void Matrix3::skew( double alpha, double beta ) |
| { |
| double fn[6]; |
| double tb = tan( beta ); |
| fn[0] = f[0] + f[2]*tb; |
| fn[1] = f[1]; |
| fn[2] = f[2] + f[3]*tb; |
| fn[3] = f[3]; |
| fn[4] = f[4] + f[5]*tb; |
| fn[5] = f[5]; |
| if( alpha != 0.0 ) |
| { |
| double ta = tan( alpha ); |
| fn[1] += f[0]*ta; |
| fn[3] += f[2]*ta; |
| fn[5] += f[4]*ta; |
| } |
| set( fn ); |
| } |
| |
| void Matrix3::scale( double sx, double sy ) |
| { |
| double fn[6]; |
| fn[0] = sx*f[0]; |
| fn[1] = sy*f[1]; |
| fn[2] = sx*f[2]; |
| fn[3] = sy*f[3]; |
| fn[4] = sx*f[4]; |
| fn[5] = sy*f[5]; |
| set( fn ); |
| } |
| |
| void Matrix3::rotate( double angle ) |
| { |
| double fn[6]; |
| double fSin = sin(angle); |
| double fCos = cos(angle); |
| fn[0] = f[0]*fCos - f[1]*fSin; |
| fn[1] = f[0]*fSin + f[1]*fCos; |
| fn[2] = f[2]*fCos - f[3]*fSin; |
| fn[3] = f[2]*fSin + f[3]*fCos; |
| fn[4] = f[4]*fCos - f[5]*fSin; |
| fn[5] = f[4]*fSin + f[5]*fCos; |
| set( fn ); |
| } |
| |
| void Matrix3::translate( double tx, double ty ) |
| { |
| f[4] += tx; |
| f[5] += ty; |
| } |
| |
| bool Matrix3::invert() |
| { |
| // short circuit trivial cases |
| if( f[1]==f[2] && f[1]==0.0 && f[0]==f[3] && f[0]==1.0 ) |
| { |
| f[4] = -f[4]; |
| f[5] = -f[5]; |
| return true; |
| } |
| |
| // check determinant |
| const double fDet = f[0]*f[3]-f[1]*f[2]; |
| if( fDet == 0.0 ) |
| return false; |
| |
| // invert the matrix |
| double fn[6]; |
| fn[0] = +f[3] / fDet; |
| fn[1] = -f[1] / fDet; |
| fn[2] = -f[2] / fDet; |
| fn[3] = +f[0] / fDet; |
| |
| // apply inversion to translation |
| fn[4] = -(f[4]*fn[0] + f[5]*fn[2]); |
| fn[5] = -(f[4]*fn[1] + f[5]*fn[3]); |
| |
| set( fn ); |
| return true; |
| } |
| |
| void Matrix3::append( PDFWriterImpl::PDFPage& rPage, OStringBuffer& rBuffer, Point* pBack ) |
| { |
| appendDouble( f[0], rBuffer ); |
| rBuffer.append( ' ' ); |
| appendDouble( f[1], rBuffer ); |
| rBuffer.append( ' ' ); |
| appendDouble( f[2], rBuffer ); |
| rBuffer.append( ' ' ); |
| appendDouble( f[3], rBuffer ); |
| rBuffer.append( ' ' ); |
| rPage.appendPoint( Point( (long)f[4], (long)f[5] ), rBuffer, false, pBack ); |
| } |
| |
| static void appendResourceMap( OStringBuffer& rBuf, const char* pPrefix, const PDFWriterImpl::ResourceMap& rList ) |
| { |
| if( rList.empty() ) |
| return; |
| rBuf.append( '/' ); |
| rBuf.append( pPrefix ); |
| rBuf.append( "<<" ); |
| int ni = 0; |
| for( PDFWriterImpl::ResourceMap::const_iterator it = rList.begin(); it != rList.end(); ++it ) |
| { |
| if( it->first.getLength() && it->second > 0 ) |
| { |
| rBuf.append( '/' ); |
| rBuf.append( it->first ); |
| rBuf.append( ' ' ); |
| rBuf.append( it->second ); |
| rBuf.append( " 0 R" ); |
| if( ((++ni) & 7) == 0 ) |
| rBuf.append( '\n' ); |
| } |
| } |
| rBuf.append( ">>\n" ); |
| } |
| |
| void PDFWriterImpl::ResourceDict::append( OStringBuffer& rBuf, sal_Int32 nFontDictObject ) |
| { |
| rBuf.append( "<</Font " ); |
| rBuf.append( nFontDictObject ); |
| rBuf.append( " 0 R\n" ); |
| appendResourceMap( rBuf, "XObject", m_aXObjects ); |
| appendResourceMap( rBuf, "ExtGState", m_aExtGStates ); |
| appendResourceMap( rBuf, "Shading", m_aShadings ); |
| appendResourceMap( rBuf, "Pattern", m_aPatterns ); |
| rBuf.append( "/ProcSet[/PDF/Text" ); |
| if( !m_aXObjects.empty() ) |
| rBuf.append( "/ImageC/ImageI/ImageB" ); |
| rBuf.append( "]\n>>\n" ); |
| }; |
| |
| PDFWriterImpl::PDFPage::PDFPage( PDFWriterImpl* pWriter, sal_Int32 nPageWidth, sal_Int32 nPageHeight, PDFWriter::Orientation eOrientation ) |
| : |
| m_pWriter( pWriter ), |
| m_nPageWidth( nPageWidth ), |
| m_nPageHeight( nPageHeight ), |
| m_eOrientation( eOrientation ), |
| m_nPageObject( 0 ), // invalid object number |
| m_nPageIndex( -1 ), // invalid index |
| m_nStreamLengthObject( 0 ), |
| m_nBeginStreamPos( 0 ), |
| m_eTransition( PDFWriter::Regular ), |
| m_nTransTime( 0 ), |
| m_nDuration( 0 ), |
| m_bHasWidgets( false ) |
| { |
| // object ref must be only ever updated in emit() |
| m_nPageObject = m_pWriter->createObject(); |
| } |
| |
| PDFWriterImpl::PDFPage::~PDFPage() |
| { |
| } |
| |
| void PDFWriterImpl::PDFPage::beginStream() |
| { |
| #if OSL_DEBUG_LEVEL > 1 |
| { |
| OStringBuffer aLine( "PDFWriterImpl::PDFPage::beginStream, +" ); |
| m_pWriter->emitComment( aLine.getStr() ); |
| } |
| #endif |
| m_aStreamObjects.push_back(m_pWriter->createObject()); |
| if( ! m_pWriter->updateObject( m_aStreamObjects.back() ) ) |
| return; |
| |
| m_nStreamLengthObject = m_pWriter->createObject(); |
| // write content stream header |
| OStringBuffer aLine; |
| aLine.append( m_aStreamObjects.back() ); |
| aLine.append( " 0 obj\n<</Length " ); |
| aLine.append( m_nStreamLengthObject ); |
| aLine.append( " 0 R" ); |
| #if defined ( COMPRESS_PAGES ) && !defined ( DEBUG_DISABLE_PDFCOMPRESSION ) |
| aLine.append( "/Filter/FlateDecode" ); |
| #endif |
| aLine.append( ">>\nstream\n" ); |
| if( ! m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ) ) |
| return; |
| if( osl_File_E_None != osl_getFilePos( m_pWriter->m_aFile, &m_nBeginStreamPos ) ) |
| { |
| osl_closeFile( m_pWriter->m_aFile ); |
| m_pWriter->m_bOpen = false; |
| } |
| #if defined ( COMPRESS_PAGES ) && !defined ( DEBUG_DISABLE_PDFCOMPRESSION ) |
| m_pWriter->beginCompression(); |
| #endif |
| m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() ); |
| } |
| |
| void PDFWriterImpl::PDFPage::endStream() |
| { |
| #if defined ( COMPRESS_PAGES ) && !defined ( DEBUG_DISABLE_PDFCOMPRESSION ) |
| m_pWriter->endCompression(); |
| #endif |
| sal_uInt64 nEndStreamPos; |
| if( osl_File_E_None != osl_getFilePos( m_pWriter->m_aFile, &nEndStreamPos ) ) |
| { |
| osl_closeFile( m_pWriter->m_aFile ); |
| m_pWriter->m_bOpen = false; |
| return; |
| } |
| m_pWriter->disableStreamEncryption(); |
| if( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n", 19 ) ) |
| return; |
| // emit stream length object |
| if( ! m_pWriter->updateObject( m_nStreamLengthObject ) ) |
| return; |
| OStringBuffer aLine; |
| aLine.append( m_nStreamLengthObject ); |
| aLine.append( " 0 obj\n" ); |
| aLine.append( (sal_Int64)(nEndStreamPos-m_nBeginStreamPos) ); |
| aLine.append( "\nendobj\n\n" ); |
| m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| bool PDFWriterImpl::PDFPage::emit(sal_Int32 nParentObject ) |
| { |
| // emit page object |
| if( ! m_pWriter->updateObject( m_nPageObject ) ) |
| return false; |
| OStringBuffer aLine; |
| |
| aLine.append( m_nPageObject ); |
| aLine.append( " 0 obj\n" |
| "<</Type/Page/Parent " ); |
| aLine.append( nParentObject ); |
| aLine.append( " 0 R" ); |
| aLine.append( "/Resources " ); |
| aLine.append( m_pWriter->getResourceDictObj() ); |
| aLine.append( " 0 R" ); |
| if( m_nPageWidth && m_nPageHeight ) |
| { |
| aLine.append( "/MediaBox[0 0 " ); |
| aLine.append( m_nPageWidth ); |
| aLine.append( ' ' ); |
| aLine.append( m_nPageHeight ); |
| aLine.append( "]" ); |
| } |
| switch( m_eOrientation ) |
| { |
| case PDFWriter::Landscape: aLine.append( "/Rotate 90\n" );break; |
| case PDFWriter::Seascape: aLine.append( "/Rotate -90\n" );break; |
| case PDFWriter::Portrait: aLine.append( "/Rotate 0\n" );break; |
| |
| case PDFWriter::Inherit: |
| default: |
| break; |
| } |
| int nAnnots = m_aAnnotations.size(); |
| if( nAnnots > 0 ) |
| { |
| aLine.append( "/Annots[\n" ); |
| for( int i = 0; i < nAnnots; i++ ) |
| { |
| aLine.append( m_aAnnotations[i] ); |
| aLine.append( " 0 R" ); |
| aLine.append( ((i+1)%15) ? " " : "\n" ); |
| } |
| aLine.append( "]\n" ); |
| } |
| #if 0 |
| // FIXME: implement tab order as Structure Tree |
| if( m_bHasWidgets && m_pWriter->getVersion() >= PDFWriter::PDF_1_5 ) |
| aLine.append( " /Tabs /S\n" ); |
| #endif |
| if( m_aMCIDParents.size() > 0 ) |
| { |
| OStringBuffer aStructParents( 1024 ); |
| aStructParents.append( "[ " ); |
| int nParents = m_aMCIDParents.size(); |
| for( int i = 0; i < nParents; i++ ) |
| { |
| aStructParents.append( m_aMCIDParents[i] ); |
| aStructParents.append( " 0 R" ); |
| aStructParents.append( ((i%10) == 9) ? "\n" : " " ); |
| } |
| aStructParents.append( "]" ); |
| m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() ); |
| |
| aLine.append( "/StructParents " ); |
| aLine.append( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) ); |
| aLine.append( "\n" ); |
| } |
| if( m_nDuration > 0 ) |
| { |
| aLine.append( "/Dur " ); |
| aLine.append( (sal_Int32)m_nDuration ); |
| aLine.append( "\n" ); |
| } |
| if( m_eTransition != PDFWriter::Regular && m_nTransTime > 0 ) |
| { |
| // transition duration |
| aLine.append( "/Trans<</D " ); |
| appendDouble( (double)m_nTransTime/1000.0, aLine, 3 ); |
| aLine.append( "\n" ); |
| const char *pStyle = NULL, *pDm = NULL, *pM = NULL, *pDi = NULL; |
| switch( m_eTransition ) |
| { |
| case PDFWriter::SplitHorizontalInward: |
| pStyle = "Split"; pDm = "H"; pM = "I"; break; |
| case PDFWriter::SplitHorizontalOutward: |
| pStyle = "Split"; pDm = "H"; pM = "O"; break; |
| case PDFWriter::SplitVerticalInward: |
| pStyle = "Split"; pDm = "V"; pM = "I"; break; |
| case PDFWriter::SplitVerticalOutward: |
| pStyle = "Split"; pDm = "V"; pM = "O"; break; |
| case PDFWriter::BlindsHorizontal: |
| pStyle = "Blinds"; pDm = "H"; break; |
| case PDFWriter::BlindsVertical: |
| pStyle = "Blinds"; pDm = "V"; break; |
| case PDFWriter::BoxInward: |
| pStyle = "Box"; pM = "I"; break; |
| case PDFWriter::BoxOutward: |
| pStyle = "Box"; pM = "O"; break; |
| case PDFWriter::WipeLeftToRight: |
| pStyle = "Wipe"; pDi = "0"; break; |
| case PDFWriter::WipeBottomToTop: |
| pStyle = "Wipe"; pDi = "90"; break; |
| case PDFWriter::WipeRightToLeft: |
| pStyle = "Wipe"; pDi = "180"; break; |
| case PDFWriter::WipeTopToBottom: |
| pStyle = "Wipe"; pDi = "270"; break; |
| case PDFWriter::Dissolve: |
| pStyle = "Dissolve"; break; |
| case PDFWriter::GlitterLeftToRight: |
| pStyle = "Glitter"; pDi = "0"; break; |
| case PDFWriter::GlitterTopToBottom: |
| pStyle = "Glitter"; pDi = "270"; break; |
| case PDFWriter::GlitterTopLeftToBottomRight: |
| pStyle = "Glitter"; pDi = "315"; break; |
| case PDFWriter::Regular: |
| break; |
| } |
| // transition style |
| if( pStyle ) |
| { |
| aLine.append( "/S/" ); |
| aLine.append( pStyle ); |
| aLine.append( "\n" ); |
| } |
| if( pDm ) |
| { |
| aLine.append( "/Dm/" ); |
| aLine.append( pDm ); |
| aLine.append( "\n" ); |
| } |
| if( pM ) |
| { |
| aLine.append( "/M/" ); |
| aLine.append( pM ); |
| aLine.append( "\n" ); |
| } |
| if( pDi ) |
| { |
| aLine.append( "/Di " ); |
| aLine.append( pDi ); |
| aLine.append( "\n" ); |
| } |
| aLine.append( ">>\n" ); |
| } |
| if( m_pWriter->getVersion() > PDFWriter::PDF_1_3 && ! m_pWriter->m_bIsPDF_A1 ) |
| { |
| aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/I true>>" ); |
| } |
| aLine.append( "/Contents" ); |
| unsigned int nStreamObjects = m_aStreamObjects.size(); |
| if( nStreamObjects > 1 ) |
| aLine.append( '[' ); |
| for( unsigned int i = 0; i < m_aStreamObjects.size(); i++ ) |
| { |
| aLine.append( ' ' ); |
| aLine.append( m_aStreamObjects[i] ); |
| aLine.append( " 0 R" ); |
| } |
| if( nStreamObjects > 1 ) |
| aLine.append( ']' ); |
| aLine.append( ">>\nendobj\n\n" ); |
| return m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| namespace vcl |
| { |
| template < class GEOMETRY > |
| GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevice* _pPixelConversion, const GEOMETRY& _rObject ) |
| { |
| GEOMETRY aPoint; |
| if ( MAP_PIXEL == _rSource.GetMapUnit() ) |
| { |
| aPoint = _pPixelConversion->PixelToLogic( _rObject, _rDest ); |
| } |
| else |
| { |
| aPoint = OutputDevice::LogicToLogic( _rObject, _rSource, _rDest ); |
| } |
| return aPoint; |
| } |
| } |
| |
| void PDFWriterImpl::PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer, bool bNeg, Point* pOutPoint ) const |
| { |
| if( pOutPoint ) |
| { |
| Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, |
| m_pWriter->m_aMapMode, |
| m_pWriter->getReferenceDevice(), |
| rPoint ) ); |
| *pOutPoint = aPoint; |
| } |
| |
| Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, |
| m_pWriter->m_aMapMode, |
| m_pWriter->getReferenceDevice(), |
| rPoint ) ); |
| |
| sal_Int32 nValue = aPoint.X(); |
| if( bNeg ) |
| nValue = -nValue; |
| |
| appendFixedInt( nValue, rBuffer ); |
| |
| rBuffer.append( ' ' ); |
| |
| nValue = pointToPixel(getHeight()) - aPoint.Y(); |
| if( bNeg ) |
| nValue = -nValue; |
| |
| appendFixedInt( nValue, rBuffer ); |
| } |
| |
| void PDFWriterImpl::PDFPage::appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const |
| { |
| double fValue = pixelToPoint(rPoint.getX()); |
| |
| appendDouble( fValue, rBuffer, nLog10Divisor ); |
| |
| rBuffer.append( ' ' ); |
| |
| fValue = double(getHeight()) - pixelToPoint(rPoint.getY()); |
| |
| appendDouble( fValue, rBuffer, nLog10Divisor ); |
| } |
| |
| void PDFWriterImpl::PDFPage::appendRect( const Rectangle& rRect, OStringBuffer& rBuffer ) const |
| { |
| appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer ); |
| rBuffer.append( ' ' ); |
| appendMappedLength( (sal_Int32)rRect.GetWidth(), rBuffer, false ); |
| rBuffer.append( ' ' ); |
| appendMappedLength( (sal_Int32)rRect.GetHeight(), rBuffer, true ); |
| rBuffer.append( " re" ); |
| } |
| |
| void PDFWriterImpl::PDFPage::convertRect( Rectangle& rRect ) const |
| { |
| Point aLL = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, |
| m_pWriter->m_aMapMode, |
| m_pWriter->getReferenceDevice(), |
| rRect.BottomLeft() + Point( 0, 1 ) |
| ); |
| Size aSize = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, |
| m_pWriter->m_aMapMode, |
| m_pWriter->getReferenceDevice(), |
| rRect.GetSize() ); |
| rRect.Left() = aLL.X(); |
| rRect.Right() = aLL.X() + aSize.Width(); |
| rRect.Top() = pointToPixel(getHeight()) - aLL.Y(); |
| rRect.Bottom() = rRect.Top() + aSize.Height(); |
| } |
| |
| void PDFWriterImpl::PDFPage::appendPolygon( const Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const |
| { |
| sal_uInt16 nPoints = rPoly.GetSize(); |
| /* |
| * #108582# applications do weird things |
| */ |
| sal_uInt32 nBufLen = rBuffer.getLength(); |
| if( nPoints > 0 ) |
| { |
| const sal_uInt8* pFlagArray = rPoly.GetConstFlagAry(); |
| appendPoint( rPoly[0], rBuffer ); |
| rBuffer.append( " m\n" ); |
| for( sal_uInt16 i = 1; i < nPoints; i++ ) |
| { |
| if( pFlagArray && pFlagArray[i] == POLY_CONTROL && nPoints-i > 2 ) |
| { |
| // bezier |
| DBG_ASSERT( pFlagArray[i+1] == POLY_CONTROL && pFlagArray[i+2] != POLY_CONTROL, "unexpected sequence of control points" ); |
| appendPoint( rPoly[i], rBuffer ); |
| rBuffer.append( " " ); |
| appendPoint( rPoly[i+1], rBuffer ); |
| rBuffer.append( " " ); |
| appendPoint( rPoly[i+2], rBuffer ); |
| rBuffer.append( " c" ); |
| i += 2; // add additionally consumed points |
| } |
| else |
| { |
| // line |
| appendPoint( rPoly[i], rBuffer ); |
| rBuffer.append( " l" ); |
| } |
| if( (rBuffer.getLength() - nBufLen) > 65 ) |
| { |
| rBuffer.append( "\n" ); |
| nBufLen = rBuffer.getLength(); |
| } |
| else |
| rBuffer.append( " " ); |
| } |
| if( bClose ) |
| rBuffer.append( "h\n" ); |
| } |
| } |
| |
| void PDFWriterImpl::PDFPage::appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const |
| { |
| basegfx::B2DPolygon aPoly( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, |
| m_pWriter->m_aMapMode, |
| m_pWriter->getReferenceDevice(), |
| rPoly ) ); |
| |
| if( basegfx::tools::isRectangle( aPoly ) ) |
| { |
| basegfx::B2DRange aRange( aPoly.getB2DRange() ); |
| basegfx::B2DPoint aBL( aRange.getMinX(), aRange.getMaxY() ); |
| appendPixelPoint( aBL, rBuffer ); |
| rBuffer.append( ' ' ); |
| appendMappedLength( aRange.getWidth(), rBuffer, false, NULL, nLog10Divisor ); |
| rBuffer.append( ' ' ); |
| appendMappedLength( aRange.getHeight(), rBuffer, true, NULL, nLog10Divisor ); |
| rBuffer.append( " re\n" ); |
| return; |
| } |
| sal_uInt32 nPoints = aPoly.count(); |
| if( nPoints > 0 ) |
| { |
| sal_uInt32 nBufLen = rBuffer.getLength(); |
| basegfx::B2DPoint aLastPoint( aPoly.getB2DPoint( 0 ) ); |
| appendPixelPoint( aLastPoint, rBuffer ); |
| rBuffer.append( " m\n" ); |
| for( sal_uInt32 i = 1; i <= nPoints; i++ ) |
| { |
| if( i != nPoints || aPoly.isClosed() ) |
| { |
| sal_uInt32 nCurPoint = i % nPoints; |
| sal_uInt32 nLastPoint = i-1; |
| basegfx::B2DPoint aPoint( aPoly.getB2DPoint( nCurPoint ) ); |
| if( aPoly.isNextControlPointUsed( nLastPoint ) && |
| aPoly.isPrevControlPointUsed( nCurPoint ) ) |
| { |
| appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer ); |
| rBuffer.append( ' ' ); |
| appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer ); |
| rBuffer.append( ' ' ); |
| appendPixelPoint( aPoint, rBuffer ); |
| rBuffer.append( " c" ); |
| } |
| else if( aPoly.isNextControlPointUsed( nLastPoint ) ) |
| { |
| appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer ); |
| rBuffer.append( ' ' ); |
| appendPixelPoint( aPoint, rBuffer ); |
| rBuffer.append( " y" ); |
| } |
| else if( aPoly.isPrevControlPointUsed( nCurPoint ) ) |
| { |
| appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer ); |
| rBuffer.append( ' ' ); |
| appendPixelPoint( aPoint, rBuffer ); |
| rBuffer.append( " v" ); |
| } |
| else |
| { |
| appendPixelPoint( aPoint, rBuffer ); |
| rBuffer.append( " l" ); |
| } |
| if( (rBuffer.getLength() - nBufLen) > 65 ) |
| { |
| rBuffer.append( "\n" ); |
| nBufLen = rBuffer.getLength(); |
| } |
| else |
| rBuffer.append( " " ); |
| } |
| } |
| if( bClose ) |
| rBuffer.append( "h\n" ); |
| } |
| } |
| |
| void PDFWriterImpl::PDFPage::appendPolyPolygon( const PolyPolygon& rPolyPoly, OStringBuffer& rBuffer, bool bClose ) const |
| { |
| sal_uInt16 nPolygons = rPolyPoly.Count(); |
| for( sal_uInt16 n = 0; n < nPolygons; n++ ) |
| appendPolygon( rPolyPoly[n], rBuffer, bClose ); |
| } |
| |
| void PDFWriterImpl::PDFPage::appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer, bool bClose ) const |
| { |
| sal_uInt32 nPolygons = rPolyPoly.count(); |
| for( sal_uInt32 n = 0; n < nPolygons; n++ ) |
| appendPolygon( rPolyPoly.getB2DPolygon( n ), rBuffer, bClose ); |
| } |
| |
| void PDFWriterImpl::PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const |
| { |
| sal_Int32 nValue = nLength; |
| if ( nLength < 0 ) |
| { |
| rBuffer.append( '-' ); |
| nValue = -nLength; |
| } |
| Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, |
| m_pWriter->m_aMapMode, |
| m_pWriter->getReferenceDevice(), |
| Size( nValue, nValue ) ) ); |
| nValue = bVertical ? aSize.Height() : aSize.Width(); |
| if( pOutLength ) |
| *pOutLength = ((nLength < 0 ) ? -nValue : nValue); |
| |
| appendFixedInt( nValue, rBuffer, 1 ); |
| } |
| |
| void PDFWriterImpl::PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength, sal_Int32 nPrecision ) const |
| { |
| Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, |
| m_pWriter->m_aMapMode, |
| m_pWriter->getReferenceDevice(), |
| Size( 1000, 1000 ) ) ); |
| if( pOutLength ) |
| *pOutLength = (sal_Int32)(fLength*(double)(bVertical ? aSize.Height() : aSize.Width())/1000.0); |
| fLength *= pixelToPoint((double)(bVertical ? aSize.Height() : aSize.Width()) / 1000.0); |
| appendDouble( fLength, rBuffer, nPrecision ); |
| } |
| |
| bool PDFWriterImpl::PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const |
| { |
| if(LINE_DASH == rInfo.GetStyle() && rInfo.GetDashLen() != rInfo.GetDotLen()) |
| { |
| // dashed and non-degraded case, check for implementation limits of dash array |
| // in PDF reader apps (e.g. acroread) |
| if(2 * (rInfo.GetDashCount() + rInfo.GetDotCount()) > 10) |
| { |
| return false; |
| } |
| } |
| |
| if(basegfx::B2DLINEJOIN_NONE != rInfo.GetLineJoin()) |
| { |
| // LineJoin used, ExtLineInfo required |
| return false; |
| } |
| |
| if(com::sun::star::drawing::LineCap_BUTT != rInfo.GetLineCap()) |
| { |
| // LineCap used, ExtLineInfo required |
| return false; |
| } |
| |
| if( rInfo.GetStyle() == LINE_DASH ) |
| { |
| rBuffer.append( "[ " ); |
| if( rInfo.GetDashLen() == rInfo.GetDotLen() ) // degraded case |
| { |
| appendMappedLength( (sal_Int32)rInfo.GetDashLen(), rBuffer ); |
| rBuffer.append( ' ' ); |
| appendMappedLength( (sal_Int32)rInfo.GetDistance(), rBuffer ); |
| rBuffer.append( ' ' ); |
| } |
| else |
| { |
| for( int n = 0; n < rInfo.GetDashCount(); n++ ) |
| { |
| appendMappedLength( (sal_Int32)rInfo.GetDashLen(), rBuffer ); |
| rBuffer.append( ' ' ); |
| appendMappedLength( (sal_Int32)rInfo.GetDistance(), rBuffer ); |
| rBuffer.append( ' ' ); |
| } |
| for( int m = 0; m < rInfo.GetDotCount(); m++ ) |
| { |
| appendMappedLength( (sal_Int32)rInfo.GetDotLen(), rBuffer ); |
| rBuffer.append( ' ' ); |
| appendMappedLength( (sal_Int32)rInfo.GetDistance(), rBuffer ); |
| rBuffer.append( ' ' ); |
| } |
| } |
| rBuffer.append( "] 0 d\n" ); |
| } |
| |
| if( rInfo.GetWidth() > 1 ) |
| { |
| appendMappedLength( (sal_Int32)rInfo.GetWidth(), rBuffer ); |
| rBuffer.append( " w\n" ); |
| } |
| else if( rInfo.GetWidth() == 0 ) |
| { |
| // "pixel" line |
| appendDouble( 72.0/double(m_pWriter->getReferenceDevice()->ImplGetDPIX()), rBuffer ); |
| rBuffer.append( " w\n" ); |
| } |
| |
| return true; |
| } |
| |
| void PDFWriterImpl::PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const |
| { |
| if( nWidth <= 0 ) |
| return; |
| if( nDelta < 1 ) |
| nDelta = 1; |
| |
| rBuffer.append( "0 " ); |
| appendMappedLength( nY, rBuffer, true ); |
| rBuffer.append( " m\n" ); |
| for( sal_Int32 n = 0; n < nWidth; ) |
| { |
| n += nDelta; |
| appendMappedLength( n, rBuffer, false ); |
| rBuffer.append( ' ' ); |
| appendMappedLength( nDelta+nY, rBuffer, true ); |
| rBuffer.append( ' ' ); |
| n += nDelta; |
| appendMappedLength( n, rBuffer, false ); |
| rBuffer.append( ' ' ); |
| appendMappedLength( nY, rBuffer, true ); |
| rBuffer.append( " v " ); |
| if( n < nWidth ) |
| { |
| n += nDelta; |
| appendMappedLength( n, rBuffer, false ); |
| rBuffer.append( ' ' ); |
| appendMappedLength( nY-nDelta, rBuffer, true ); |
| rBuffer.append( ' ' ); |
| n += nDelta; |
| appendMappedLength( n, rBuffer, false ); |
| rBuffer.append( ' ' ); |
| appendMappedLength( nY, rBuffer, true ); |
| rBuffer.append( " v\n" ); |
| } |
| } |
| rBuffer.append( "S\n" ); |
| } |
| |
| /* |
| * class PDFWriterImpl |
| */ |
| |
| PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext, |
| const com::sun::star::uno::Reference< com::sun::star::beans::XMaterialHolder >& xEnc, |
| PDFWriter& i_rOuterFace) |
| : |
| m_pReferenceDevice( NULL ), |
| m_aMapMode( MAP_POINT, Point(), Fraction( 1L, pointToPixel(1) ), Fraction( 1L, pointToPixel(1) ) ), |
| m_nCurrentStructElement( 0 ), |
| m_bEmitStructure( true ), |
| m_bNewMCID( false ), |
| m_nCurrentControl( -1 ), |
| m_bEmbedStandardFonts( false ), |
| m_nNextFID( 1 ), |
| m_nInheritedPageWidth( 595 ), // default A4 |
| m_nInheritedPageHeight( 842 ), // default A4 |
| m_eInheritedOrientation( PDFWriter::Portrait ), |
| m_nCurrentPage( -1 ), |
| m_nResourceDict( -1 ), |
| m_nFontDictObject( -1 ), |
| m_pCodec( NULL ), |
| m_aDocDigest( rtl_digest_createMD5() ), |
| m_aCipher( (rtlCipher)NULL ), |
| m_aDigest( NULL ), |
| m_bEncryptThisStream( false ), |
| m_pEncryptionBuffer( NULL ), |
| m_nEncryptionBufferSize( 0 ), |
| m_bIsPDF_A1( false ), |
| m_rOuterFace( i_rOuterFace ) |
| { |
| #ifdef DO_TEST_PDF |
| static bool bOnce = true; |
| if( bOnce ) |
| { |
| bOnce = false; |
| doTestCode(); |
| } |
| #endif |
| m_aContext = rContext; |
| m_aStructure.push_back( PDFStructureElement() ); |
| m_aStructure[0].m_nOwnElement = 0; |
| m_aStructure[0].m_nParentElement = 0; |
| |
| Font aFont; |
| aFont.SetName( String( RTL_CONSTASCII_USTRINGPARAM( "Times" ) ) ); |
| aFont.SetSize( Size( 0, 12 ) ); |
| |
| GraphicsState aState; |
| aState.m_aMapMode = m_aMapMode; |
| aState.m_aFont = aFont; |
| m_aGraphicsStack.push_front( aState ); |
| |
| oslFileError aError = osl_openFile( m_aContext.URL.pData, &m_aFile, osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ); |
| if( aError != osl_File_E_None ) |
| { |
| if( aError == osl_File_E_EXIST ) |
| { |
| aError = osl_openFile( m_aContext.URL.pData, &m_aFile, osl_File_OpenFlag_Write ); |
| if( aError == osl_File_E_None ) |
| aError = osl_setFileSize( m_aFile, 0 ); |
| } |
| } |
| if( aError != osl_File_E_None ) |
| return; |
| |
| m_bOpen = true; |
| |
| // setup DocInfo |
| setupDocInfo(); |
| |
| /* prepare the cypher engine, can be done in CTOR, free in DTOR */ |
| m_aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream ); |
| m_aDigest = rtl_digest_createMD5(); |
| |
| /* the size of the Codec default maximum */ |
| checkEncryptionBufferSize( 0x4000 ); |
| |
| if( xEnc.is() ) |
| prepareEncryption( xEnc ); |
| |
| if( m_aContext.Encryption.Encrypt() ) |
| { |
| // sanity check |
| if( m_aContext.Encryption.OValue.size() != ENCRYPTED_PWD_SIZE || |
| m_aContext.Encryption.UValue.size() != ENCRYPTED_PWD_SIZE || |
| m_aContext.Encryption.EncryptionKey.size() != MAXIMUM_RC4_KEY_LENGTH |
| ) |
| { |
| // the field lengths are invalid ? This was not setup by initEncryption. |
| // do not encrypt after all |
| m_aContext.Encryption.OValue.clear(); |
| m_aContext.Encryption.UValue.clear(); |
| OSL_ENSURE( 0, "encryption data failed sanity check, encryption disabled" ); |
| } |
| else // setup key lengths |
| m_nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, m_nKeyLength, m_nRC4KeyLength ); |
| } |
| |
| // write header |
| OStringBuffer aBuffer( 20 ); |
| aBuffer.append( "%PDF-" ); |
| switch( m_aContext.Version ) |
| { |
| case PDFWriter::PDF_1_2: aBuffer.append( "1.2" );break; |
| case PDFWriter::PDF_1_3: aBuffer.append( "1.3" );break; |
| case PDFWriter::PDF_A_1: |
| default: |
| case PDFWriter::PDF_1_4: aBuffer.append( "1.4" );break; |
| case PDFWriter::PDF_1_5: aBuffer.append( "1.5" );break; |
| } |
| // append something binary as comment (suggested in PDF Reference) |
| aBuffer.append( "\n%äüöß\n" ); |
| if( !writeBuffer( aBuffer.getStr(), aBuffer.getLength() ) ) |
| { |
| osl_closeFile( m_aFile ); |
| m_bOpen = false; |
| return; |
| } |
| |
| // insert outline root |
| m_aOutline.push_back( PDFOutlineEntry() ); |
| |
| m_bIsPDF_A1 = (m_aContext.Version == PDFWriter::PDF_A_1); |
| if( m_bIsPDF_A1 ) |
| m_aContext.Version = PDFWriter::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour |
| |
| m_bEmbedStandardFonts = m_aContext.EmbedStandardFonts; |
| } |
| |
| PDFWriterImpl::~PDFWriterImpl() |
| { |
| if( m_aDocDigest ) |
| rtl_digest_destroyMD5( m_aDocDigest ); |
| delete static_cast<VirtualDevice*>(m_pReferenceDevice); |
| |
| if( m_aCipher ) |
| rtl_cipher_destroyARCFOUR( m_aCipher ); |
| if( m_aDigest ) |
| rtl_digest_destroyMD5( m_aDigest ); |
| |
| rtl_freeMemory( m_pEncryptionBuffer ); |
| } |
| |
| void PDFWriterImpl::setupDocInfo() |
| { |
| std::vector< sal_uInt8 > aId; |
| computeDocumentIdentifier( aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aCreationMetaDateString ); |
| if( m_aContext.Encryption.DocumentIdentifier.empty() ) |
| m_aContext.Encryption.DocumentIdentifier = aId; |
| } |
| |
| void PDFWriterImpl::computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier, |
| const vcl::PDFWriter::PDFDocInfo& i_rDocInfo, |
| rtl::OString& o_rCString1, |
| rtl::OString& o_rCString2 |
| ) |
| { |
| o_rIdentifier.clear(); |
| |
| //build the document id |
| rtl::OString aInfoValuesOut; |
| OStringBuffer aID( 1024 ); |
| if( i_rDocInfo.Title.Len() ) |
| appendUnicodeTextString( i_rDocInfo.Title, aID ); |
| if( i_rDocInfo.Author.Len() ) |
| appendUnicodeTextString( i_rDocInfo.Author, aID ); |
| if( i_rDocInfo.Subject.Len() ) |
| appendUnicodeTextString( i_rDocInfo.Subject, aID ); |
| if( i_rDocInfo.Keywords.Len() ) |
| appendUnicodeTextString( i_rDocInfo.Keywords, aID ); |
| if( i_rDocInfo.Creator.Len() ) |
| appendUnicodeTextString( i_rDocInfo.Creator, aID ); |
| if( i_rDocInfo.Producer.Len() ) |
| appendUnicodeTextString( i_rDocInfo.Producer, aID ); |
| |
| TimeValue aTVal, aGMT; |
| oslDateTime aDT; |
| osl_getSystemTime( &aGMT ); |
| osl_getLocalTimeFromSystemTime( &aGMT, &aTVal ); |
| osl_getDateTimeFromTimeValue( &aTVal, &aDT ); |
| rtl::OStringBuffer aCreationDateString(64), aCreationMetaDateString(64); |
| aCreationDateString.append( "D:" ); |
| aCreationDateString.append( (sal_Char)('0' + ((aDT.Year/1000)%10)) ); |
| aCreationDateString.append( (sal_Char)('0' + ((aDT.Year/100)%10)) ); |
| aCreationDateString.append( (sal_Char)('0' + ((aDT.Year/10)%10)) ); |
| aCreationDateString.append( (sal_Char)('0' + ((aDT.Year)%10)) ); |
| aCreationDateString.append( (sal_Char)('0' + ((aDT.Month/10)%10)) ); |
| aCreationDateString.append( (sal_Char)('0' + ((aDT.Month)%10)) ); |
| aCreationDateString.append( (sal_Char)('0' + ((aDT.Day/10)%10)) ); |
| aCreationDateString.append( (sal_Char)('0' + ((aDT.Day)%10)) ); |
| aCreationDateString.append( (sal_Char)('0' + ((aDT.Hours/10)%10)) ); |
| aCreationDateString.append( (sal_Char)('0' + ((aDT.Hours)%10)) ); |
| aCreationDateString.append( (sal_Char)('0' + ((aDT.Minutes/10)%10)) ); |
| aCreationDateString.append( (sal_Char)('0' + ((aDT.Minutes)%10)) ); |
| aCreationDateString.append( (sal_Char)('0' + ((aDT.Seconds/10)%10)) ); |
| aCreationDateString.append( (sal_Char)('0' + ((aDT.Seconds)%10)) ); |
| |
| //--> i59651, we fill the Metadata date string as well, if PDF/A is requested |
| // according to ISO 19005-1:2005 6.7.3 the date is corrected for |
| // local time zone offset UTC only, whereas Acrobat 8 seems |
| // to use the localtime notation only |
| // according to a raccomandation in XMP Specification (Jan 2004, page 75) |
| // the Acrobat way seems the right approach |
| aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Year/1000)%10)) ); |
| aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Year/100)%10)) ); |
| aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Year/10)%10)) ); |
| aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Year)%10)) ); |
| aCreationMetaDateString.append( "-" ); |
| aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Month/10)%10)) ); |
| aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Month)%10)) ); |
| aCreationMetaDateString.append( "-" ); |
| aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Day/10)%10)) ); |
| aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Day)%10)) ); |
| aCreationMetaDateString.append( "T" ); |
| aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Hours/10)%10)) ); |
| aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Hours)%10)) ); |
| aCreationMetaDateString.append( ":" ); |
| aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Minutes/10)%10)) ); |
| aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Minutes)%10)) ); |
| aCreationMetaDateString.append( ":" ); |
| aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Seconds/10)%10)) ); |
| aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Seconds)%10)) ); |
| |
| sal_uInt32 nDelta = 0; |
| if( aGMT.Seconds > aTVal.Seconds ) |
| { |
| aCreationDateString.append( "-" ); |
| nDelta = aGMT.Seconds-aTVal.Seconds; |
| aCreationMetaDateString.append( "-" ); |
| } |
| else if( aGMT.Seconds < aTVal.Seconds ) |
| { |
| aCreationDateString.append( "+" ); |
| nDelta = aTVal.Seconds-aGMT.Seconds; |
| aCreationMetaDateString.append( "+" ); |
| } |
| else |
| { |
| aCreationDateString.append( "Z" ); |
| aCreationMetaDateString.append( "Z" ); |
| |
| } |
| if( nDelta ) |
| { |
| aCreationDateString.append( (sal_Char)('0' + ((nDelta/36000)%10)) ); |
| aCreationDateString.append( (sal_Char)('0' + ((nDelta/3600)%10)) ); |
| aCreationDateString.append( "'" ); |
| aCreationDateString.append( (sal_Char)('0' + ((nDelta/600)%6)) ); |
| aCreationDateString.append( (sal_Char)('0' + ((nDelta/60)%10)) ); |
| |
| aCreationMetaDateString.append( (sal_Char)('0' + ((nDelta/36000)%10)) ); |
| aCreationMetaDateString.append( (sal_Char)('0' + ((nDelta/3600)%10)) ); |
| aCreationMetaDateString.append( ":" ); |
| aCreationMetaDateString.append( (sal_Char)('0' + ((nDelta/600)%6)) ); |
| aCreationMetaDateString.append( (sal_Char)('0' + ((nDelta/60)%10)) ); |
| } |
| aCreationDateString.append( "'" ); |
| aID.append( aCreationDateString.getStr(), aCreationDateString.getLength() ); |
| |
| aInfoValuesOut = aID.makeStringAndClear(); |
| o_rCString1 = aCreationDateString.makeStringAndClear(); |
| o_rCString2 = aCreationMetaDateString.makeStringAndClear(); |
| |
| rtlDigest aDigest = rtl_digest_createMD5(); |
| OSL_ENSURE( aDigest != NULL, "PDFWriterImpl::computeDocumentIdentifier: cannot obtain a digest object !" ); |
| if( aDigest ) |
| { |
| rtlDigestError nError = rtl_digest_updateMD5( aDigest, &aGMT, sizeof( aGMT ) ); |
| if( nError == rtl_Digest_E_None ) |
| nError = rtl_digest_updateMD5( aDigest, aInfoValuesOut.getStr(), aInfoValuesOut.getLength() ); |
| if( nError == rtl_Digest_E_None ) |
| { |
| o_rIdentifier = std::vector< sal_uInt8 >( 16, 0 ); |
| //the binary form of the doc id is needed for encryption stuff |
| rtl_digest_getMD5( aDigest, &o_rIdentifier[0], 16 ); |
| } |
| } |
| } |
| |
| /* i12626 methods */ |
| /* |
| check if the Unicode string must be encrypted or not, perform the requested task, |
| append the string as unicode hex, encrypted if needed |
| */ |
| inline void PDFWriterImpl::appendUnicodeTextStringEncrypt( const rtl::OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer ) |
| { |
| rOutBuffer.append( "<" ); |
| if( m_aContext.Encryption.Encrypt() ) |
| { |
| const sal_Unicode* pStr = rInString.getStr(); |
| sal_Int32 nLen = rInString.getLength(); |
| //prepare a unicode string, encrypt it |
| if( checkEncryptionBufferSize( nLen*2 ) ) |
| { |
| enableStringEncryption( nInObjectNumber ); |
| register sal_uInt8 *pCopy = m_pEncryptionBuffer; |
| sal_Int32 nChars = 2; |
| *pCopy++ = 0xFE; |
| *pCopy++ = 0xFF; |
| // we need to prepare a byte stream from the unicode string buffer |
| for( register int i = 0; i < nLen; i++ ) |
| { |
| register sal_Unicode aUnChar = pStr[i]; |
| *pCopy++ = (sal_uInt8)( aUnChar >> 8 ); |
| *pCopy++ = (sal_uInt8)( aUnChar & 255 ); |
| nChars += 2; |
| } |
| //encrypt in place |
| rtl_cipher_encodeARCFOUR( m_aCipher, m_pEncryptionBuffer, nChars, m_pEncryptionBuffer, nChars ); |
| //now append, hexadecimal (appendHex), the encrypted result |
| for(register int i = 0; i < nChars; i++) |
| appendHex( m_pEncryptionBuffer[i], rOutBuffer ); |
| } |
| } |
| else |
| appendUnicodeTextString( rInString, rOutBuffer ); |
| rOutBuffer.append( ">" ); |
| } |
| |
| inline void PDFWriterImpl::appendLiteralStringEncrypt( rtl::OStringBuffer& rInString, const sal_Int32 nInObjectNumber, rtl::OStringBuffer& rOutBuffer ) |
| { |
| rOutBuffer.append( "(" ); |
| sal_Int32 nChars = rInString.getLength(); |
| //check for encryption, if ok, encrypt the string, then convert with appndLiteralString |
| if( m_aContext.Encryption.Encrypt() && checkEncryptionBufferSize( nChars ) ) |
| { |
| //encrypt the string in a buffer, then append it |
| enableStringEncryption( nInObjectNumber ); |
| rtl_cipher_encodeARCFOUR( m_aCipher, rInString.getStr(), nChars, m_pEncryptionBuffer, nChars ); |
| appendLiteralString( (const sal_Char*)m_pEncryptionBuffer, nChars, rOutBuffer ); |
| } |
| else |
| appendLiteralString( rInString.getStr(), nChars , rOutBuffer ); |
| rOutBuffer.append( ")" ); |
| } |
| |
| inline void PDFWriterImpl::appendLiteralStringEncrypt( const rtl::OString& rInString, const sal_Int32 nInObjectNumber, rtl::OStringBuffer& rOutBuffer ) |
| { |
| rtl::OStringBuffer aBufferString( rInString ); |
| appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer); |
| } |
| |
| void PDFWriterImpl::appendLiteralStringEncrypt( const rtl::OUString& rInString, const sal_Int32 nInObjectNumber, rtl::OStringBuffer& rOutBuffer, rtl_TextEncoding nEnc ) |
| { |
| rtl::OString aBufferString( rtl::OUStringToOString( rInString, nEnc ) ); |
| sal_Int32 nLen = aBufferString.getLength(); |
| rtl::OStringBuffer aBuf( nLen ); |
| const sal_Char* pT = aBufferString.getStr(); |
| |
| for( sal_Int32 i = 0; i < nLen; i++, pT++ ) |
| { |
| if( (*pT & 0x80) == 0 ) |
| aBuf.append( *pT ); |
| else |
| { |
| aBuf.append( '<' ); |
| appendHex( *pT, aBuf ); |
| aBuf.append( '>' ); |
| } |
| } |
| aBufferString = aBuf.makeStringAndClear(); |
| appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer); |
| } |
| |
| /* end i12626 methods */ |
| |
| void PDFWriterImpl::emitComment( const char* pComment ) |
| { |
| OStringBuffer aLine( 64 ); |
| aLine.append( "% " ); |
| aLine.append( (const sal_Char*)pComment ); |
| aLine.append( "\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| bool PDFWriterImpl::compressStream( SvMemoryStream* pStream ) |
| { |
| #ifndef DEBUG_DISABLE_PDFCOMPRESSION |
| pStream->Seek( STREAM_SEEK_TO_END ); |
| sal_uLong nEndPos = pStream->Tell(); |
| pStream->Seek( STREAM_SEEK_TO_BEGIN ); |
| ZCodec* pCodec = new ZCodec( 0x4000, 0x4000 ); |
| SvMemoryStream aStream; |
| pCodec->BeginCompression(); |
| pCodec->Write( aStream, (const sal_uInt8*)pStream->GetData(), nEndPos ); |
| pCodec->EndCompression(); |
| delete pCodec; |
| nEndPos = aStream.Tell(); |
| pStream->Seek( STREAM_SEEK_TO_BEGIN ); |
| aStream.Seek( STREAM_SEEK_TO_BEGIN ); |
| pStream->SetStreamSize( nEndPos ); |
| pStream->Write( aStream.GetData(), nEndPos ); |
| return true; |
| #else |
| (void)pStream; |
| return false; |
| #endif |
| } |
| |
| void PDFWriterImpl::beginCompression() |
| { |
| #ifndef DEBUG_DISABLE_PDFCOMPRESSION |
| m_pCodec = new ZCodec( 0x4000, 0x4000 ); |
| m_pMemStream = new SvMemoryStream(); |
| m_pCodec->BeginCompression(); |
| #endif |
| } |
| |
| void PDFWriterImpl::endCompression() |
| { |
| #ifndef DEBUG_DISABLE_PDFCOMPRESSION |
| if( m_pCodec ) |
| { |
| m_pCodec->EndCompression(); |
| delete m_pCodec; |
| m_pCodec = NULL; |
| sal_uInt64 nLen = m_pMemStream->Tell(); |
| m_pMemStream->Seek( 0 ); |
| writeBuffer( m_pMemStream->GetData(), nLen ); |
| delete m_pMemStream; |
| m_pMemStream = NULL; |
| } |
| #endif |
| } |
| |
| bool PDFWriterImpl::writeBuffer( const void* pBuffer, sal_uInt64 nBytes ) |
| { |
| if( ! m_bOpen ) // we are already down the drain |
| return false; |
| |
| if( ! nBytes ) // huh ? |
| return true; |
| |
| if( m_aOutputStreams.begin() != m_aOutputStreams.end() ) |
| { |
| m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END ); |
| m_aOutputStreams.front().m_pStream->Write( pBuffer, sal::static_int_cast<sal_Size>(nBytes) ); |
| return true; |
| } |
| |
| sal_uInt64 nWritten; |
| if( m_pCodec ) |
| { |
| m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), (sal_uLong)nBytes ); |
| nWritten = nBytes; |
| } |
| else |
| { |
| sal_Bool buffOK = sal_True; |
| if( m_bEncryptThisStream ) |
| { |
| /* implement the encryption part of the PDF spec encryption algorithm 3.1 */ |
| if( ( buffOK = checkEncryptionBufferSize( static_cast<sal_Int32>(nBytes) ) ) != sal_False ) |
| rtl_cipher_encodeARCFOUR( m_aCipher, |
| (sal_uInt8*)pBuffer, static_cast<sal_Size>(nBytes), |
| m_pEncryptionBuffer, static_cast<sal_Size>(nBytes) ); |
| } |
| |
| const void* pWriteBuffer = ( m_bEncryptThisStream && buffOK ) ? m_pEncryptionBuffer : pBuffer; |
| if( m_aDocDigest ) |
| rtl_digest_updateMD5( m_aDocDigest, pWriteBuffer, static_cast<sal_uInt32>(nBytes) ); |
| |
| if( osl_writeFile( m_aFile, |
| pWriteBuffer, |
| nBytes, &nWritten ) != osl_File_E_None ) |
| nWritten = 0; |
| |
| if( nWritten != nBytes ) |
| { |
| osl_closeFile( m_aFile ); |
| m_bOpen = false; |
| } |
| } |
| |
| return nWritten == nBytes; |
| } |
| |
| OutputDevice* PDFWriterImpl::getReferenceDevice() |
| { |
| if( ! m_pReferenceDevice ) |
| { |
| VirtualDevice* pVDev = new VirtualDevice( 0 ); |
| |
| m_pReferenceDevice = pVDev; |
| |
| if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 ) |
| pVDev->SetReferenceDevice( VirtualDevice::REFDEV_MODE_PDF1 ); |
| else |
| pVDev->SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy ); |
| |
| pVDev->SetOutputSizePixel( Size( 640, 480 ) ); |
| pVDev->SetMapMode( MAP_MM ); |
| |
| m_pReferenceDevice->mpPDFWriter = this; |
| m_pReferenceDevice->ImplUpdateFontData( sal_True ); |
| } |
| return m_pReferenceDevice; |
| } |
| |
| class ImplPdfBuiltinFontData : public ImplFontData |
| { |
| private: |
| const PDFWriterImpl::BuiltinFont& mrBuiltin; |
| |
| public: |
| enum {PDF_FONT_MAGIC = 0xBDFF0A1C }; |
| ImplPdfBuiltinFontData( const PDFWriterImpl::BuiltinFont& ); |
| const PDFWriterImpl::BuiltinFont* GetBuiltinFont() const { return &mrBuiltin; } |
| |
| virtual ImplFontData* Clone() const { return new ImplPdfBuiltinFontData(*this); } |
| virtual ImplFontEntry* CreateFontInstance( ImplFontSelectData& ) const; |
| virtual sal_IntPtr GetFontId() const { return reinterpret_cast<sal_IntPtr>(&mrBuiltin); } |
| }; |
| |
| inline const ImplPdfBuiltinFontData* GetPdfFontData( const ImplFontData* pFontData ) |
| { |
| const ImplPdfBuiltinFontData* pFD = NULL; |
| if( pFontData && pFontData->CheckMagic( ImplPdfBuiltinFontData::PDF_FONT_MAGIC ) ) |
| pFD = static_cast<const ImplPdfBuiltinFontData*>( pFontData ); |
| return pFD; |
| } |
| |
| static ImplDevFontAttributes GetDevFontAttributes( const PDFWriterImpl::BuiltinFont& rBuiltin ) |
| { |
| ImplDevFontAttributes aDFA; |
| aDFA.maName = String::CreateFromAscii( rBuiltin.m_pName ); |
| aDFA.maStyleName = String::CreateFromAscii( rBuiltin.m_pStyleName ); |
| aDFA.meFamily = rBuiltin.m_eFamily; |
| aDFA.mbSymbolFlag = (rBuiltin.m_eCharSet != RTL_TEXTENCODING_MS_1252 ); |
| aDFA.mePitch = rBuiltin.m_ePitch; |
| aDFA.meWeight = rBuiltin.m_eWeight; |
| aDFA.meItalic = rBuiltin.m_eItalic; |
| aDFA.meWidthType = rBuiltin.m_eWidthType; |
| |
| aDFA.mbOrientation = true; |
| aDFA.mbDevice = true; |
| aDFA.mnQuality = 50000; |
| aDFA.mbSubsettable = false; |
| aDFA.mbEmbeddable = false; |
| return aDFA; |
| } |
| |
| ImplPdfBuiltinFontData::ImplPdfBuiltinFontData( const PDFWriterImpl::BuiltinFont& rBuiltin ) |
| : ImplFontData( GetDevFontAttributes(rBuiltin), PDF_FONT_MAGIC ), |
| mrBuiltin( rBuiltin ) |
| {} |
| |
| ImplFontEntry* ImplPdfBuiltinFontData::CreateFontInstance( ImplFontSelectData& rFSD ) const |
| { |
| ImplFontEntry* pEntry = new ImplFontEntry( rFSD ); |
| return pEntry; |
| } |
| |
| ImplDevFontList* PDFWriterImpl::filterDevFontList( ImplDevFontList* pFontList ) |
| { |
| DBG_ASSERT( m_aSubsets.size() == 0, "Fonts changing during PDF generation, document will be invalid" ); |
| ImplDevFontList* pFiltered = pFontList->Clone( true, true ); |
| |
| // append the PDF builtin fonts |
| if( !m_bIsPDF_A1 && !m_bEmbedStandardFonts) |
| for( unsigned int i = 0; i < sizeof(m_aBuiltinFonts)/sizeof(m_aBuiltinFonts[0]); i++ ) |
| { |
| ImplFontData* pNewData = new ImplPdfBuiltinFontData( m_aBuiltinFonts[i] ); |
| pFiltered->Add( pNewData ); |
| } |
| return pFiltered; |
| } |
| |
| bool PDFWriterImpl::isBuiltinFont( const ImplFontData* pFont ) const |
| { |
| const ImplPdfBuiltinFontData* pFD = GetPdfFontData( pFont ); |
| return (pFD != NULL); |
| } |
| |
| void PDFWriterImpl::getFontMetric( ImplFontSelectData* pSelect, ImplFontMetricData* pMetric ) const |
| { |
| const ImplPdfBuiltinFontData* pFD = GetPdfFontData( pSelect->mpFontData ); |
| if( !pFD ) |
| return; |
| const BuiltinFont* pBuiltinFont = pFD->GetBuiltinFont(); |
| |
| pMetric->mnOrientation = sal::static_int_cast<short>(pSelect->mnOrientation); |
| pMetric->meFamily = pBuiltinFont->m_eFamily; |
| pMetric->mePitch = pBuiltinFont->m_ePitch; |
| pMetric->meWeight = pBuiltinFont->m_eWeight; |
| pMetric->meItalic = pBuiltinFont->m_eItalic; |
| pMetric->mbSymbolFlag = pFD->IsSymbolFont(); |
| pMetric->mnWidth = pSelect->mnHeight; |
| pMetric->mnAscent = ( pSelect->mnHeight * +pBuiltinFont->m_nAscent + 500 ) / 1000; |
| pMetric->mnDescent = ( pSelect->mnHeight * -pBuiltinFont->m_nDescent + 500 ) / 1000; |
| pMetric->mnIntLeading = 0; |
| pMetric->mnExtLeading = 0; |
| pMetric->mnSlant = 0; |
| pMetric->mbScalableFont = true; |
| pMetric->mbDevice = true; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| namespace vcl { |
| |
| class PDFSalLayout : public GenericSalLayout |
| { |
| PDFWriterImpl& mrPDFWriterImpl; |
| const PDFWriterImpl::BuiltinFont& mrBuiltinFont; |
| bool mbIsSymbolFont; |
| long mnPixelPerEM; |
| String maOrigText; |
| |
| public: |
| PDFSalLayout( PDFWriterImpl&, |
| const PDFWriterImpl::BuiltinFont&, |
| long nPixelPerEM, int nOrientation ); |
| |
| void SetText( const String& rText ) { maOrigText = rText; } |
| virtual bool LayoutText( ImplLayoutArgs& ); |
| virtual void InitFont() const; |
| virtual void DrawText( SalGraphics& ) const; |
| }; |
| |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| PDFSalLayout::PDFSalLayout( PDFWriterImpl& rPDFWriterImpl, |
| const PDFWriterImpl::BuiltinFont& rBuiltinFont, |
| long nPixelPerEM, int nOrientation ) |
| : mrPDFWriterImpl( rPDFWriterImpl ), |
| mrBuiltinFont( rBuiltinFont ), |
| mnPixelPerEM( nPixelPerEM ) |
| { |
| mbIsSymbolFont = (rBuiltinFont.m_eCharSet != RTL_TEXTENCODING_MS_1252); |
| SetOrientation( nOrientation ); |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| bool PDFSalLayout::LayoutText( ImplLayoutArgs& rArgs ) |
| { |
| const String aText( rArgs.mpStr+rArgs.mnMinCharPos, sal::static_int_cast<xub_StrLen>(rArgs.mnEndCharPos-rArgs.mnMinCharPos) ); |
| SetText( aText ); |
| SetUnitsPerPixel( 1000 ); |
| |
| rtl_UnicodeToTextConverter aConv = rtl_createTextToUnicodeConverter( mrBuiltinFont.m_eCharSet ); |
| |
| Point aNewPos( 0, 0 ); |
| bool bRightToLeft; |
| for( int nCharPos = -1; rArgs.GetNextPos( &nCharPos, &bRightToLeft ); ) |
| { |
| // TODO: handle unicode surrogates |
| // on the other hand the PDF builtin fonts don't support them anyway |
| sal_Unicode cChar = rArgs.mpStr[ nCharPos ]; |
| if( bRightToLeft ) |
| cChar = static_cast<sal_Unicode>(GetMirroredChar( cChar )); |
| |
| if( 1 ) // TODO: shortcut for ASCII? |
| { |
| sal_Char aBuf[4]; |
| sal_uInt32 nInfo; |
| sal_Size nSrcCvtChars; |
| |
| sal_Size nConv = rtl_convertUnicodeToText( aConv, |
| NULL, |
| &cChar, 1, |
| aBuf, sizeof(aBuf)/sizeof(*aBuf), |
| RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR, |
| &nInfo, &nSrcCvtChars ); |
| // check whether conversion was possible |
| // else fallback font is needed as the standard fonts |
| // are handled via WinAnsi encoding |
| if( nConv > 0 ) |
| cChar = ((sal_Unicode)aBuf[0]) & 0x00ff; |
| } |
| if( cChar & 0xff00 ) |
| { |
| cChar = 0; // NotDef glyph |
| rArgs.NeedFallback( nCharPos, bRightToLeft ); |
| } |
| |
| long nGlyphWidth = (long)mrBuiltinFont.m_aWidths[cChar] * mnPixelPerEM; |
| long nGlyphFlags = 0; // builtin fonts don't have diacritic glyphs |
| if( bRightToLeft ) |
| nGlyphFlags |= GlyphItem::IS_RTL_GLYPH; |
| // TODO: get kerning from builtin fonts |
| GlyphItem aGI( nCharPos, cChar, aNewPos, nGlyphFlags, nGlyphWidth ); |
| AppendGlyph( aGI ); |
| |
| aNewPos.X() += nGlyphWidth; |
| } |
| |
| rtl_destroyUnicodeToTextConverter( aConv ); |
| |
| return true; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void PDFSalLayout::InitFont() const |
| { |
| // TODO: recreate font with all its attributes |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void PDFSalLayout::DrawText( SalGraphics& ) const |
| { |
| mrPDFWriterImpl.drawLayout( *const_cast<PDFSalLayout*>(this), maOrigText, true ); |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| SalLayout* PDFWriterImpl::GetTextLayout( ImplLayoutArgs& rArgs, ImplFontSelectData* pSelect ) |
| { |
| DBG_ASSERT( (pSelect->mpFontData != NULL), |
| "PDFWriterImpl::GetTextLayout mpFontData is NULL" ); |
| |
| const ImplPdfBuiltinFontData* pFD = GetPdfFontData( pSelect->mpFontData ); |
| if( !pFD ) |
| return NULL; |
| const BuiltinFont* pBuiltinFont = pFD->GetBuiltinFont(); |
| |
| long nPixelPerEM = pSelect->mnWidth ? pSelect->mnWidth : pSelect->mnHeight; |
| int nOrientation = pSelect->mnOrientation; |
| PDFSalLayout* pLayout = new PDFSalLayout( *this, *pBuiltinFont, nPixelPerEM, nOrientation ); |
| pLayout->SetText( rArgs.mpStr ); |
| return pLayout; |
| } |
| |
| sal_Int32 PDFWriterImpl::newPage( sal_Int32 nPageWidth, sal_Int32 nPageHeight, PDFWriter::Orientation eOrientation ) |
| { |
| endPage(); |
| m_nCurrentPage = m_aPages.size(); |
| m_aPages.push_back( PDFPage(this, nPageWidth, nPageHeight, eOrientation ) ); |
| m_aPages.back().m_nPageIndex = m_nCurrentPage; |
| m_aPages.back().beginStream(); |
| |
| // setup global graphics state |
| // linewidth is "1 pixel" by default |
| OStringBuffer aBuf( 16 ); |
| appendDouble( 72.0/double(getReferenceDevice()->ImplGetDPIX()), aBuf ); |
| aBuf.append( " w\n" ); |
| writeBuffer( aBuf.getStr(), aBuf.getLength() ); |
| |
| return m_nCurrentPage; |
| } |
| |
| void PDFWriterImpl::endPage() |
| { |
| if( m_aPages.begin() != m_aPages.end() ) |
| { |
| // close eventual MC sequence |
| endStructureElementMCSeq(); |
| |
| // sanity check |
| if( m_aOutputStreams.begin() != m_aOutputStreams.end() ) |
| { |
| DBG_ERROR( "redirection across pages !!!" ); |
| m_aOutputStreams.clear(); // leak ! |
| m_aMapMode.SetOrigin( Point() ); |
| } |
| |
| m_aGraphicsStack.clear(); |
| m_aGraphicsStack.push_back( GraphicsState() ); |
| |
| // this should pop the PDF graphics stack if necessary |
| updateGraphicsState(); |
| |
| m_aPages.back().endStream(); |
| |
| // reset the default font |
| Font aFont; |
| aFont.SetName( String( RTL_CONSTASCII_USTRINGPARAM( "Times" ) ) ); |
| aFont.SetSize( Size( 0, 12 ) ); |
| |
| m_aCurrentPDFState = m_aGraphicsStack.front(); |
| m_aGraphicsStack.front().m_aFont = aFont; |
| |
| for( std::list<BitmapEmit>::iterator it = m_aBitmaps.begin(); |
| it != m_aBitmaps.end(); ++it ) |
| { |
| if( ! it->m_aBitmap.IsEmpty() ) |
| { |
| writeBitmapObject( *it ); |
| it->m_aBitmap = BitmapEx(); |
| } |
| } |
| for( std::list<JPGEmit>::iterator jpeg = m_aJPGs.begin(); jpeg != m_aJPGs.end(); ++jpeg ) |
| { |
| if( jpeg->m_pStream ) |
| { |
| writeJPG( *jpeg ); |
| delete jpeg->m_pStream; |
| jpeg->m_pStream = NULL; |
| jpeg->m_aMask = Bitmap(); |
| } |
| } |
| for( std::list<TransparencyEmit>::iterator t = m_aTransparentObjects.begin(); |
| t != m_aTransparentObjects.end(); ++t ) |
| { |
| if( t->m_pContentStream ) |
| { |
| writeTransparentObject( *t ); |
| delete t->m_pContentStream; |
| t->m_pContentStream = NULL; |
| } |
| } |
| } |
| } |
| |
| sal_Int32 PDFWriterImpl::createObject() |
| { |
| m_aObjects.push_back( ~0U ); |
| return m_aObjects.size(); |
| } |
| |
| bool PDFWriterImpl::updateObject( sal_Int32 n ) |
| { |
| if( ! m_bOpen ) |
| return false; |
| |
| sal_uInt64 nOffset = ~0U; |
| oslFileError aError = osl_getFilePos( m_aFile, &nOffset ); |
| DBG_ASSERT( aError == osl_File_E_None, "could not register object" ); |
| if( aError != osl_File_E_None ) |
| { |
| osl_closeFile( m_aFile ); |
| m_bOpen = false; |
| } |
| m_aObjects[ n-1 ] = nOffset; |
| return aError == osl_File_E_None; |
| } |
| |
| #define CHECK_RETURN( x ) if( !(x) ) return 0 |
| |
| sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject ) |
| { |
| if( nObject > 0 ) |
| { |
| OStringBuffer aLine( 1024 ); |
| |
| aLine.append( nObject ); |
| aLine.append( " 0 obj\n" |
| "<</Nums[\n" ); |
| sal_Int32 nTreeItems = m_aStructParentTree.size(); |
| for( sal_Int32 n = 0; n < nTreeItems; n++ ) |
| { |
| aLine.append( n ); |
| aLine.append( ' ' ); |
| aLine.append( m_aStructParentTree[n] ); |
| aLine.append( "\n" ); |
| } |
| aLine.append( "]>>\nendobj\n\n" ); |
| CHECK_RETURN( updateObject( nObject ) ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| } |
| return nObject; |
| } |
| |
| const sal_Char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr ) |
| { |
| static std::map< PDFWriter::StructAttribute, const char* > aAttributeStrings; |
| // fill maps once |
| if( aAttributeStrings.empty() ) |
| { |
| aAttributeStrings[ PDFWriter::Placement ] = "Placement"; |
| aAttributeStrings[ PDFWriter::WritingMode ] = "WritingMode"; |
| aAttributeStrings[ PDFWriter::SpaceBefore ] = "SpaceBefore"; |
| aAttributeStrings[ PDFWriter::SpaceAfter ] = "SpaceAfter"; |
| aAttributeStrings[ PDFWriter::StartIndent ] = "StartIndent"; |
| aAttributeStrings[ PDFWriter::EndIndent ] = "EndIndent"; |
| aAttributeStrings[ PDFWriter::TextIndent ] = "TextIndent"; |
| aAttributeStrings[ PDFWriter::TextAlign ] = "TextAlign"; |
| aAttributeStrings[ PDFWriter::Width ] = "Width"; |
| aAttributeStrings[ PDFWriter::Height ] = "Height"; |
| aAttributeStrings[ PDFWriter::BlockAlign ] = "BlockAlign"; |
| aAttributeStrings[ PDFWriter::InlineAlign ] = "InlineAlign"; |
| aAttributeStrings[ PDFWriter::LineHeight ] = "LineHeight"; |
| aAttributeStrings[ PDFWriter::BaselineShift ] = "BaselineShift"; |
| aAttributeStrings[ PDFWriter::TextDecorationType ] = "TextDecorationType"; |
| aAttributeStrings[ PDFWriter::ListNumbering ] = "ListNumbering"; |
| aAttributeStrings[ PDFWriter::RowSpan ] = "RowSpan"; |
| aAttributeStrings[ PDFWriter::ColSpan ] = "ColSpan"; |
| aAttributeStrings[ PDFWriter::LinkAnnotation ] = "LinkAnnotation"; |
| } |
| |
| std::map< PDFWriter::StructAttribute, const char* >::const_iterator it = |
| aAttributeStrings.find( eAttr ); |
| |
| #if OSL_DEBUG_LEVEL > 1 |
| if( it == aAttributeStrings.end() ) |
| fprintf( stderr, "invalid PDFWriter::StructAttribute %d\n", eAttr ); |
| #endif |
| |
| return it != aAttributeStrings.end() ? it->second : ""; |
| } |
| |
| const sal_Char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal ) |
| { |
| static std::map< PDFWriter::StructAttributeValue, const char* > aValueStrings; |
| |
| if( aValueStrings.empty() ) |
| { |
| aValueStrings[ PDFWriter::NONE ] = "None"; |
| aValueStrings[ PDFWriter::Block ] = "Block"; |
| aValueStrings[ PDFWriter::Inline ] = "Inline"; |
| aValueStrings[ PDFWriter::Before ] = "Before"; |
| aValueStrings[ PDFWriter::After ] = "After"; |
| aValueStrings[ PDFWriter::Start ] = "Start"; |
| aValueStrings[ PDFWriter::End ] = "End"; |
| aValueStrings[ PDFWriter::LrTb ] = "LrTb"; |
| aValueStrings[ PDFWriter::RlTb ] = "RlTb"; |
| aValueStrings[ PDFWriter::TbRl ] = "TbRl"; |
| aValueStrings[ PDFWriter::Center ] = "Center"; |
| aValueStrings[ PDFWriter::Justify ] = "Justify"; |
| aValueStrings[ PDFWriter::Auto ] = "Auto"; |
| aValueStrings[ PDFWriter::Middle ] = "Middle"; |
| aValueStrings[ PDFWriter::Normal ] = "Normal"; |
| aValueStrings[ PDFWriter::Underline ] = "Underline"; |
| aValueStrings[ PDFWriter::Overline ] = "Overline"; |
| aValueStrings[ PDFWriter::LineThrough ] = "LineThrough"; |
| aValueStrings[ PDFWriter::Disc ] = "Disc"; |
| aValueStrings[ PDFWriter::Circle ] = "Circle"; |
| aValueStrings[ PDFWriter::Square ] = "Square"; |
| aValueStrings[ PDFWriter::Decimal ] = "Decimal"; |
| aValueStrings[ PDFWriter::UpperRoman ] = "UpperRoman"; |
| aValueStrings[ PDFWriter::LowerRoman ] = "LowerRoman"; |
| aValueStrings[ PDFWriter::UpperAlpha ] = "UpperAlpha"; |
| aValueStrings[ PDFWriter::LowerAlpha ] = "LowerAlpha"; |
| } |
| |
| std::map< PDFWriter::StructAttributeValue, const char* >::const_iterator it = |
| aValueStrings.find( eVal ); |
| |
| #if OSL_DEBUG_LEVEL > 1 |
| if( it == aValueStrings.end() ) |
| fprintf( stderr, "invalid PDFWriter::StructAttributeValue %d\n", eVal ); |
| #endif |
| |
| return it != aValueStrings.end() ? it->second : ""; |
| } |
| |
| static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFWriterImpl::PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt ) |
| { |
| o_rLine.append( "/" ); |
| o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) ); |
| |
| if( i_rVal.eValue != PDFWriter::Invalid ) |
| { |
| o_rLine.append( "/" ); |
| o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) ); |
| } |
| else |
| { |
| // numerical value |
| o_rLine.append( " " ); |
| if( i_bIsFixedInt ) |
| appendFixedInt( i_rVal.nValue, o_rLine ); |
| else |
| o_rLine.append( i_rVal.nValue ); |
| } |
| o_rLine.append( "\n" ); |
| } |
| |
| OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle ) |
| { |
| // create layout, list and table attribute sets |
| OStringBuffer aLayout(256), aList(64), aTable(64); |
| for( PDFStructAttributes::const_iterator it = i_rEle.m_aAttributes.begin(); |
| it != i_rEle.m_aAttributes.end(); ++it ) |
| { |
| if( it->first == PDFWriter::ListNumbering ) |
| appendStructureAttributeLine( it->first, it->second, aList, true ); |
| else if( it->first == PDFWriter::RowSpan || |
| it->first == PDFWriter::ColSpan ) |
| appendStructureAttributeLine( it->first, it->second, aTable, false ); |
| else if( it->first == PDFWriter::LinkAnnotation ) |
| { |
| sal_Int32 nLink = it->second.nValue; |
| std::map< sal_Int32, sal_Int32 >::const_iterator link_it = |
| m_aLinkPropertyMap.find( nLink ); |
| if( link_it != m_aLinkPropertyMap.end() ) |
| nLink = link_it->second; |
| if( nLink >= 0 && nLink < (sal_Int32)m_aLinks.size() ) |
| { |
| // update struct parent of link |
| OStringBuffer aStructParentEntry( 32 ); |
| aStructParentEntry.append( i_rEle.m_nObject ); |
| aStructParentEntry.append( " 0 R" ); |
| m_aStructParentTree.push_back( aStructParentEntry.makeStringAndClear() ); |
| m_aLinks[ nLink ].m_nStructParent = m_aStructParentTree.size()-1; |
| |
| sal_Int32 nRefObject = createObject(); |
| OStringBuffer aRef( 256 ); |
| aRef.append( nRefObject ); |
| aRef.append( " 0 obj\n" |
| "<</Type/OBJR/Obj " ); |
| aRef.append( m_aLinks[ nLink ].m_nObject ); |
| aRef.append( " 0 R>>\n" |
| "endobj\n\n" |
| ); |
| updateObject( nRefObject ); |
| writeBuffer( aRef.getStr(), aRef.getLength() ); |
| |
| i_rEle.m_aKids.push_back( PDFStructureElementKid( nRefObject ) ); |
| } |
| else |
| { |
| DBG_ERROR( "unresolved link id for Link structure" ); |
| #if OSL_DEBUG_LEVEL > 1 |
| fprintf( stderr, "unresolved link id %" SAL_PRIdINT32 " for Link structure\n", nLink ); |
| { |
| OStringBuffer aLine( "unresolved link id " ); |
| aLine.append( nLink ); |
| aLine.append( " for Link structure" ); |
| emitComment( aLine.getStr() ); |
| } |
| #endif |
| } |
| } |
| else |
| appendStructureAttributeLine( it->first, it->second, aLayout, true ); |
| } |
| if( ! i_rEle.m_aBBox.IsEmpty() ) |
| { |
| aLayout.append( "/BBox[" ); |
| appendFixedInt( i_rEle.m_aBBox.Left(), aLayout ); |
| aLayout.append( " " ); |
| appendFixedInt( i_rEle.m_aBBox.Top(), aLayout ); |
| aLayout.append( " " ); |
| appendFixedInt( i_rEle.m_aBBox.Right(), aLayout ); |
| aLayout.append( " " ); |
| appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout ); |
| aLayout.append( "]\n" ); |
| } |
| |
| std::vector< sal_Int32 > aAttribObjects; |
| if( aLayout.getLength() ) |
| { |
| aAttribObjects.push_back( createObject() ); |
| updateObject( aAttribObjects.back() ); |
| OStringBuffer aObj( 64 ); |
| aObj.append( aAttribObjects.back() ); |
| aObj.append( " 0 obj\n" |
| "<</O/Layout\n" ); |
| aLayout.append( ">>\nendobj\n\n" ); |
| writeBuffer( aObj.getStr(), aObj.getLength() ); |
| writeBuffer( aLayout.getStr(), aLayout.getLength() ); |
| } |
| if( aList.getLength() ) |
| { |
| aAttribObjects.push_back( createObject() ); |
| updateObject( aAttribObjects.back() ); |
| OStringBuffer aObj( 64 ); |
| aObj.append( aAttribObjects.back() ); |
| aObj.append( " 0 obj\n" |
| "<</O/List\n" ); |
| aList.append( ">>\nendobj\n\n" ); |
| writeBuffer( aObj.getStr(), aObj.getLength() ); |
| writeBuffer( aList.getStr(), aList.getLength() ); |
| } |
| if( aTable.getLength() ) |
| { |
| aAttribObjects.push_back( createObject() ); |
| updateObject( aAttribObjects.back() ); |
| OStringBuffer aObj( 64 ); |
| aObj.append( aAttribObjects.back() ); |
| aObj.append( " 0 obj\n" |
| "<</O/Table\n" ); |
| aTable.append( ">>\nendobj\n\n" ); |
| writeBuffer( aObj.getStr(), aObj.getLength() ); |
| writeBuffer( aTable.getStr(), aTable.getLength() ); |
| } |
| |
| OStringBuffer aRet( 64 ); |
| if( aAttribObjects.size() > 1 ) |
| aRet.append( " [" ); |
| for( std::vector< sal_Int32 >::const_iterator at_it = aAttribObjects.begin(); |
| at_it != aAttribObjects.end(); ++at_it ) |
| { |
| aRet.append( " " ); |
| aRet.append( *at_it ); |
| aRet.append( " 0 R" ); |
| } |
| if( aAttribObjects.size() > 1 ) |
| aRet.append( " ]" ); |
| return aRet.makeStringAndClear(); |
| } |
| |
| sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) |
| { |
| if( |
| // do not emit NonStruct and its children |
| rEle.m_eType == PDFWriter::NonStructElement && |
| rEle.m_nOwnElement != rEle.m_nParentElement // but of course emit the struct tree root |
| ) |
| return 0; |
| |
| for( std::list< sal_Int32 >::const_iterator it = rEle.m_aChildren.begin(); it != rEle.m_aChildren.end(); ++it ) |
| { |
| if( *it > 0 && *it < sal_Int32(m_aStructure.size()) ) |
| { |
| PDFStructureElement& rChild = m_aStructure[ *it ]; |
| if( rChild.m_eType != PDFWriter::NonStructElement ) |
| { |
| if( rChild.m_nParentElement == rEle.m_nOwnElement ) |
| emitStructure( rChild ); |
| else |
| { |
| DBG_ERROR( "PDFWriterImpl::emitStructure: invalid child structure element" ); |
| #if OSL_DEBUG_LEVEL > 1 |
| fprintf( stderr, "PDFWriterImpl::emitStructure: invalid child structure elemnt with id %" SAL_PRIdINT32 "\n", *it ); |
| #endif |
| } |
| } |
| } |
| else |
| { |
| DBG_ERROR( "PDFWriterImpl::emitStructure: invalid child structure id" ); |
| #if OSL_DEBUG_LEVEL > 1 |
| fprintf( stderr, "PDFWriterImpl::emitStructure: invalid child structure id %" SAL_PRIdINT32 "\n", *it ); |
| #endif |
| } |
| } |
| |
| OStringBuffer aLine( 512 ); |
| aLine.append( rEle.m_nObject ); |
| aLine.append( " 0 obj\n" |
| "<</Type" ); |
| sal_Int32 nParentTree = -1; |
| if( rEle.m_nOwnElement == rEle.m_nParentElement ) |
| { |
| nParentTree = createObject(); |
| CHECK_RETURN( nParentTree ); |
| aLine.append( "/StructTreeRoot\n" ); |
| aLine.append( "/ParentTree " ); |
| aLine.append( nParentTree ); |
| aLine.append( " 0 R\n" ); |
| if( ! m_aRoleMap.empty() ) |
| { |
| aLine.append( "/RoleMap<<" ); |
| for( std::hash_map<OString,OString,OStringHash>::const_iterator |
| it = m_aRoleMap.begin(); it != m_aRoleMap.end(); ++it ) |
| { |
| aLine.append( '/' ); |
| aLine.append(it->first); |
| aLine.append( '/' ); |
| aLine.append( it->second ); |
| aLine.append( '\n' ); |
| } |
| aLine.append( ">>\n" ); |
| } |
| } |
| else |
| { |
| aLine.append( "/StructElem\n" |
| "/S/" ); |
| if( rEle.m_aAlias.getLength() > 0 ) |
| aLine.append( rEle.m_aAlias ); |
| else |
| aLine.append( getStructureTag( rEle.m_eType ) ); |
| aLine.append( "\n" |
| "/P " ); |
| aLine.append( m_aStructure[ rEle.m_nParentElement ].m_nObject ); |
| aLine.append( " 0 R\n" |
| "/Pg " ); |
| aLine.append( rEle.m_nFirstPageObject ); |
| aLine.append( " 0 R\n" ); |
| if( rEle.m_aActualText.getLength() ) |
| { |
| aLine.append( "/ActualText" ); |
| appendUnicodeTextStringEncrypt( rEle.m_aActualText, rEle.m_nObject, aLine ); |
| aLine.append( "\n" ); |
| } |
| if( rEle.m_aAltText.getLength() ) |
| { |
| aLine.append( "/Alt" ); |
| appendUnicodeTextStringEncrypt( rEle.m_aAltText, rEle.m_nObject, aLine ); |
| aLine.append( "\n" ); |
| } |
| } |
| if( ! rEle.m_aBBox.IsEmpty() || rEle.m_aAttributes.size() ) |
| { |
| OString aAttribs = emitStructureAttributes( rEle ); |
| if( aAttribs.getLength() ) |
| { |
| aLine.append( "/A" ); |
| aLine.append( aAttribs ); |
| aLine.append( "\n" ); |
| } |
| } |
| if( rEle.m_aLocale.Language.getLength() > 0 ) |
| { |
| OUStringBuffer aLocBuf( 16 ); |
| aLocBuf.append( rEle.m_aLocale.Language.toAsciiLowerCase() ); |
| if( rEle.m_aLocale.Country.getLength() > 0 ) |
| { |
| aLocBuf.append( sal_Unicode('-') ); |
| aLocBuf.append( rEle.m_aLocale.Country ); |
| } |
| aLine.append( "/Lang" ); |
| appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), rEle.m_nObject, aLine ); |
| aLine.append( "\n" ); |
| } |
| if( ! rEle.m_aKids.empty() ) |
| { |
| unsigned int i = 0; |
| aLine.append( "/K[" ); |
| for( std::list< PDFStructureElementKid >::const_iterator it = |
| rEle.m_aKids.begin(); it != rEle.m_aKids.end(); ++it, i++ ) |
| { |
| if( it->nMCID == -1 ) |
| { |
| aLine.append( it->nObject ); |
| aLine.append( " 0 R" ); |
| aLine.append( ( (i & 15) == 15 ) ? "\n" : " " ); |
| } |
| else |
| { |
| if( it->nObject == rEle.m_nFirstPageObject ) |
| { |
| aLine.append( it->nMCID ); |
| aLine.append( " " ); |
| } |
| else |
| { |
| aLine.append( "<</Type/MCR/Pg " ); |
| aLine.append( it->nObject ); |
| aLine.append( " 0 R /MCID " ); |
| aLine.append( it->nMCID ); |
| aLine.append( ">>\n" ); |
| } |
| } |
| } |
| aLine.append( "]\n" ); |
| } |
| aLine.append( ">>\nendobj\n\n" ); |
| |
| CHECK_RETURN( updateObject( rEle.m_nObject ) ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| CHECK_RETURN( emitStructParentTree( nParentTree ) ); |
| |
| return rEle.m_nObject; |
| } |
| |
| bool PDFWriterImpl::emitGradients() |
| { |
| for( std::list<GradientEmit>::iterator it = m_aGradients.begin(); |
| it != m_aGradients.end(); ++it ) |
| { |
| CHECK_RETURN( writeGradientFunction( *it ) ); |
| } |
| return true; |
| } |
| |
| bool PDFWriterImpl::emitTilings() |
| { |
| OStringBuffer aTilingObj( 1024 ); |
| |
| for( std::vector<TilingEmit>::iterator it = m_aTilings.begin(); it != m_aTilings.end(); ++it ) |
| { |
| DBG_ASSERT( it->m_pTilingStream, "tiling without stream" ); |
| if( ! it->m_pTilingStream ) |
| continue; |
| |
| aTilingObj.setLength( 0 ); |
| |
| #if OSL_DEBUG_LEVEL > 1 |
| emitComment( "PDFWriterImpl::emitTilings" ); |
| #endif |
| |
| sal_Int32 nX = (sal_Int32)it->m_aRectangle.Left(); |
| sal_Int32 nY = (sal_Int32)it->m_aRectangle.Top(); |
| sal_Int32 nW = (sal_Int32)it->m_aRectangle.GetWidth(); |
| sal_Int32 nH = (sal_Int32)it->m_aRectangle.GetHeight(); |
| if( it->m_aCellSize.Width() == 0 ) |
| it->m_aCellSize.Width() = nW; |
| if( it->m_aCellSize.Height() == 0 ) |
| it->m_aCellSize.Height() = nH; |
| |
| bool bDeflate = compressStream( it->m_pTilingStream ); |
| it->m_pTilingStream->Seek( STREAM_SEEK_TO_END ); |
| sal_Size nTilingStreamSize = it->m_pTilingStream->Tell(); |
| it->m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN ); |
| |
| // write pattern object |
| aTilingObj.append( it->m_nObject ); |
| aTilingObj.append( " 0 obj\n" ); |
| aTilingObj.append( "<</Type/Pattern/PatternType 1\n" |
| "/PaintType 1\n" |
| "/TilingType 2\n" |
| "/BBox[" ); |
| appendFixedInt( nX, aTilingObj ); |
| aTilingObj.append( ' ' ); |
| appendFixedInt( nY, aTilingObj ); |
| aTilingObj.append( ' ' ); |
| appendFixedInt( nX+nW, aTilingObj ); |
| aTilingObj.append( ' ' ); |
| appendFixedInt( nY+nH, aTilingObj ); |
| aTilingObj.append( "]\n" |
| "/XStep " ); |
| appendFixedInt( it->m_aCellSize.Width(), aTilingObj ); |
| aTilingObj.append( "\n" |
| "/YStep " ); |
| appendFixedInt( it->m_aCellSize.Height(), aTilingObj ); |
| aTilingObj.append( "\n" ); |
| if( it->m_aTransform.matrix[0] != 1.0 || |
| it->m_aTransform.matrix[1] != 0.0 || |
| it->m_aTransform.matrix[3] != 0.0 || |
| it->m_aTransform.matrix[4] != 1.0 || |
| it->m_aTransform.matrix[2] != 0.0 || |
| it->m_aTransform.matrix[5] != 0.0 ) |
| { |
| aTilingObj.append( "/Matrix [" ); |
| // TODO: scaling, mirroring on y, etc |
| appendDouble( it->m_aTransform.matrix[0], aTilingObj ); |
| aTilingObj.append( ' ' ); |
| appendDouble( it->m_aTransform.matrix[1], aTilingObj ); |
| aTilingObj.append( ' ' ); |
| appendDouble( it->m_aTransform.matrix[3], aTilingObj ); |
| aTilingObj.append( ' ' ); |
| appendDouble( it->m_aTransform.matrix[4], aTilingObj ); |
| aTilingObj.append( ' ' ); |
| appendDouble( it->m_aTransform.matrix[2], aTilingObj ); |
| aTilingObj.append( ' ' ); |
| appendDouble( it->m_aTransform.matrix[5], aTilingObj ); |
| aTilingObj.append( "]\n" ); |
| } |
| aTilingObj.append( "/Resources" ); |
| it->m_aResources.append( aTilingObj, getFontDictObject() ); |
| if( bDeflate ) |
| aTilingObj.append( "/Filter/FlateDecode" ); |
| aTilingObj.append( "/Length " ); |
| aTilingObj.append( (sal_Int32)nTilingStreamSize ); |
| aTilingObj.append( ">>\nstream\n" ); |
| CHECK_RETURN( updateObject( it->m_nObject ) ); |
| CHECK_RETURN( writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ); |
| checkAndEnableStreamEncryption( it->m_nObject ); |
| nTilingStreamSize = writeBuffer( it->m_pTilingStream->GetData(), nTilingStreamSize ); |
| delete it->m_pTilingStream; |
| it->m_pTilingStream = NULL; |
| if( nTilingStreamSize == 0 ) |
| return false; |
| disableStreamEncryption(); |
| aTilingObj.setLength( 0 ); |
| aTilingObj.append( "\nendstream\nendobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ); |
| } |
| return true; |
| } |
| |
| sal_Int32 PDFWriterImpl::emitBuiltinFont( const ImplFontData* pFont, sal_Int32 nFontObject ) |
| { |
| const ImplPdfBuiltinFontData* pFD = GetPdfFontData( pFont ); |
| if( !pFD ) |
| return 0; |
| const BuiltinFont* pBuiltinFont = pFD->GetBuiltinFont(); |
| |
| OStringBuffer aLine( 1024 ); |
| |
| if( nFontObject <= 0 ) |
| nFontObject = createObject(); |
| CHECK_RETURN( updateObject( nFontObject ) ); |
| aLine.append( nFontObject ); |
| aLine.append( " 0 obj\n" |
| "<</Type/Font/Subtype/Type1/BaseFont/" ); |
| appendName( pBuiltinFont->m_pPSName, aLine ); |
| aLine.append( "\n" ); |
| if( pBuiltinFont->m_eCharSet == RTL_TEXTENCODING_MS_1252 ) |
| aLine.append( "/Encoding/WinAnsiEncoding\n" ); |
| aLine.append( ">>\nendobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| return nFontObject; |
| } |
| |
| std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const ImplFontData* pFont, EmbedFont& rEmbed ) |
| { |
| std::map< sal_Int32, sal_Int32 > aRet; |
| if( isBuiltinFont( pFont ) ) |
| { |
| aRet[ rEmbed.m_nNormalFontID ] = emitBuiltinFont( pFont ); |
| return aRet; |
| } |
| |
| sal_Int32 nFontObject = 0; |
| sal_Int32 nFontDescriptor = 0; |
| rtl::OString aSubType( "/Type1" ); |
| FontSubsetInfo aInfo; |
| // fill in dummy values |
| aInfo.m_nAscent = 1000; |
| aInfo.m_nDescent = 200; |
| aInfo.m_nCapHeight = 1000; |
| aInfo.m_aFontBBox = Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) ); |
| aInfo.m_aPSName = pFont->maName; |
| sal_Int32 pWidths[256]; |
| rtl_zeroMemory( pWidths, sizeof(pWidths) ); |
| if( pFont->IsEmbeddable() ) |
| { |
| const unsigned char* pFontData = NULL; |
| long nFontLen = 0; |
| sal_Ucs nEncodedCodes[256]; |
| sal_Int32 pEncWidths[256]; |
| if( (pFontData = (const unsigned char*)m_pReferenceDevice->mpGraphics->GetEmbedFontData( pFont, nEncodedCodes, pEncWidths, aInfo, &nFontLen )) != NULL ) |
| { |
| m_pReferenceDevice->mpGraphics->FreeEmbedFontData( pFontData, nFontLen ); |
| for( int i = 0; i < 256; i++ ) |
| { |
| if( nEncodedCodes[i] >= 32 && nEncodedCodes[i] < 256 ) |
| { |
| pWidths[i] = pEncWidths[ i ]; |
| } |
| } |
| } |
| } |
| else if( pFont->mbSubsettable ) |
| { |
| aSubType = rtl::OString( "/TrueType" ); |
| Int32Vector aGlyphWidths; |
| Ucs2UIntMap aUnicodeMap; |
| m_pReferenceDevice->mpGraphics->GetGlyphWidths( pFont, false, aGlyphWidths, aUnicodeMap ); |
| |
| OUString aTmpName; |
| osl_createTempFile( NULL, NULL, &aTmpName.pData ); |
| sal_GlyphId aGlyphIds[ 256 ]; |
| sal_uInt8 pEncoding[ 256 ]; |
| sal_Ucs pUnicodes[ 256 ]; |
| sal_Int32 pDuWidths[ 256 ]; |
| |
| memset( aGlyphIds, 0, sizeof( aGlyphIds ) ); |
| memset( pEncoding, 0, sizeof( pEncoding ) ); |
| memset( pUnicodes, 0, sizeof( pUnicodes ) ); |
| memset( pDuWidths, 0, sizeof( pDuWidths ) ); |
| |
| for( sal_Ucs c = 32; c < 256; c++ ) |
| { |
| pUnicodes[c] = c; |
| pEncoding[c] = c; |
| aGlyphIds[c] = 0; |
| if( aUnicodeMap.find( c ) != aUnicodeMap.end() ) |
| pWidths[ c ] = aGlyphWidths[ aUnicodeMap[ c ] ]; |
| } |
| |
| m_pReferenceDevice->mpGraphics->CreateFontSubset( aTmpName, pFont, aGlyphIds, pEncoding, pDuWidths, 256, aInfo ); |
| osl_removeFile( aTmpName.pData ); |
| } |
| else |
| { |
| DBG_ERROR( "system font neither embeddable nor subsettable" ); |
| } |
| |
| // write font descriptor |
| nFontDescriptor = emitFontDescriptor( pFont, aInfo, 0, 0 ); |
| if( nFontDescriptor ) |
| { |
| // write font object |
| sal_Int32 nObject = createObject(); |
| if( updateObject( nObject ) ) |
| { |
| OStringBuffer aLine( 1024 ); |
| aLine.append( nObject ); |
| aLine.append( " 0 obj\n" |
| "<</Type/Font/Subtype" ); |
| aLine.append( aSubType ); |
| aLine.append( "/BaseFont/" ); |
| appendName( aInfo.m_aPSName, aLine ); |
| aLine.append( "\n" ); |
| if( !pFont->mbSymbolFlag ) |
| aLine.append( "/Encoding/WinAnsiEncoding\n" ); |
| aLine.append( "/FirstChar 32 /LastChar 255\n" |
| "/Widths[" ); |
| for( int i = 32; i < 256; i++ ) |
| { |
| aLine.append( pWidths[i] ); |
| aLine.append( ((i&15) == 15) ? "\n" : " " ); |
| } |
| aLine.append( "]\n" |
| "/FontDescriptor " ); |
| aLine.append( nFontDescriptor ); |
| aLine.append( " 0 R>>\n" |
| "endobj\n\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| |
| nFontObject = nObject; |
| aRet[ rEmbed.m_nNormalFontID ] = nObject; |
| } |
| } |
| |
| return aRet; |
| } |
| |
| typedef int ThreeInts[3]; |
| static bool getPfbSegmentLengths( const unsigned char* pFontBytes, int nByteLen, |
| ThreeInts& rSegmentLengths ) |
| { |
| if( !pFontBytes || (nByteLen < 0) ) |
| return false; |
| const unsigned char* pPtr = pFontBytes; |
| const unsigned char* pEnd = pFontBytes + nByteLen; |
| |
| for( int i = 0; i < 3; ++i) { |
| // read segment1 header |
| if( pPtr+6 >= pEnd ) |
| return false; |
| if( (pPtr[0] != 0x80) || (pPtr[1] >= 0x03) ) |
| return false; |
| const int nLen = (pPtr[5]<<24) + (pPtr[4]<<16) + (pPtr[3]<<8) + pPtr[2]; |
| if( nLen <= 0) |
| return false; |
| rSegmentLengths[i] = nLen; |
| pPtr += nLen + 6; |
| } |
| |
| // read segment-end header |
| if( pPtr+2 >= pEnd ) |
| return false; |
| if( (pPtr[0] != 0x80) || (pPtr[1] != 0x03) ) |
| return false; |
| |
| return true; |
| } |
| |
| struct FontException : public std::exception |
| { |
| }; |
| |
| // TODO: always subset instead of embedding the full font => this method becomes obsolete then |
| std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitEmbeddedFont( const ImplFontData* pFont, EmbedFont& rEmbed ) |
| { |
| std::map< sal_Int32, sal_Int32 > aRet; |
| if( isBuiltinFont( pFont ) ) |
| { |
| aRet[ rEmbed.m_nNormalFontID ] = emitBuiltinFont( pFont ); |
| return aRet; |
| } |
| |
| sal_Int32 nFontObject = 0; |
| sal_Int32 nStreamObject = 0; |
| sal_Int32 nFontDescriptor = 0; |
| |
| // prepare font encoding |
| const Ucs2SIntMap* pEncoding = m_pReferenceDevice->mpGraphics->GetFontEncodingVector( pFont, NULL ); |
| sal_Int32 nToUnicodeStream = 0; |
| sal_uInt8 nEncoding[256]; |
| sal_Ucs nEncodedCodes[256]; |
| std::vector<sal_Ucs> aUnicodes; |
| aUnicodes.reserve( 256 ); |
| sal_Int32 pUnicodesPerGlyph[256]; |
| sal_Int32 pEncToUnicodeIndex[256]; |
| if( pEncoding ) |
| { |
| rtl_zeroMemory( nEncoding, sizeof(nEncoding) ); |
| rtl_zeroMemory( nEncodedCodes, sizeof(nEncodedCodes) ); |
| rtl_zeroMemory( pUnicodesPerGlyph, sizeof(pUnicodesPerGlyph) ); |
| rtl_zeroMemory( pEncToUnicodeIndex, sizeof(pEncToUnicodeIndex) ); |
| for( Ucs2SIntMap::const_iterator it = pEncoding->begin(); it != pEncoding->end(); ++it ) |
| { |
| if( it->second != -1 ) |
| { |
| sal_Int32 nCode = (sal_Int32)(it->second & 0x000000ff); |
| nEncoding[ nCode ] = static_cast<sal_uInt8>( nCode ); |
| nEncodedCodes[ nCode ] = it->first; |
| pEncToUnicodeIndex[ nCode ] = static_cast<sal_Int32>(aUnicodes.size()); |
| aUnicodes.push_back( it->first ); |
| pUnicodesPerGlyph[ nCode ] = 1; |
| } |
| } |
| } |
| |
| FontSubsetInfo aInfo; |
| sal_Int32 pWidths[256]; |
| const unsigned char* pFontData = NULL; |
| long nFontLen = 0; |
| sal_Int32 nLength1, nLength2; |
| try |
| { |
| if( (pFontData = (const unsigned char*)m_pReferenceDevice->mpGraphics->GetEmbedFontData( pFont, nEncodedCodes, pWidths, aInfo, &nFontLen )) != NULL ) |
| { |
| if( (aInfo.m_nFontType & FontSubsetInfo::ANY_TYPE1) == 0 ) |
| throw FontException(); |
| // see whether it is pfb or pfa; if it is a pfb, fill ranges |
| // of 6 bytes that are not part of the font program |
| std::list< int > aSections; |
| std::list< int >::const_iterator it; |
| int nIndex = 0; |
| while( pFontData[nIndex] == 0x80 && nIndex < nFontLen-1 ) |
| { |
| aSections.push_back( nIndex ); |
| if( pFontData[nIndex+1] == 0x03 ) |
| break; |
| sal_Int32 nBytes = |
| ((sal_Int32)pFontData[nIndex+2]) | |
| ((sal_Int32)pFontData[nIndex+3]) << 8 | |
| ((sal_Int32)pFontData[nIndex+4]) << 16 | |
| ((sal_Int32)pFontData[nIndex+5]) << 24; |
| nIndex += nBytes+6; |
| } |
| |
| // search for eexec |
| // TODO: use getPfbSegmentLengths() if possible to skip the search thingies below |
| nIndex = 0; |
| int nEndAsciiIndex; |
| int nBeginBinaryIndex; |
| int nEndBinaryIndex; |
| do |
| { |
| while( nIndex < nFontLen-4 && |
| ( pFontData[nIndex] != 'e' || |
| pFontData[nIndex+1] != 'e' || |
| pFontData[nIndex+2] != 'x' || |
| pFontData[nIndex+3] != 'e' || |
| pFontData[nIndex+4] != 'c' |
| ) |
| ) |
| nIndex++; |
| // check whether we are in a excluded section |
| for( it = aSections.begin(); it != aSections.end() && (nIndex < *it || nIndex > ((*it) + 5) ); ++it ) |
| ; |
| } while( it != aSections.end() && nIndex < nFontLen-4 ); |
| // this should end the ascii part |
| if( nIndex > nFontLen-5 ) |
| throw FontException(); |
| |
| nEndAsciiIndex = nIndex+4; |
| // now count backwards until we can account for 512 '0' |
| // which is the endmarker of the (hopefully) binary data |
| // do not count the pfb header sections |
| int nFound = 0; |
| nIndex = nFontLen-1; |
| while( nIndex > 0 && nFound < 512 ) |
| { |
| for( it = aSections.begin(); it != aSections.end() && (nIndex < *it || nIndex > ((*it) + 5) ); ++it ) |
| ; |
| if( it == aSections.end() ) |
| { |
| // inside the 512 '0' block there may only be whitespace |
| // according to T1 spec; probably it would be to simple |
| // if all fonts complied |
| if( pFontData[nIndex] == '0' ) |
| nFound++; |
| else if( nFound > 0 && |
| pFontData[nIndex] != '\r' && |
| pFontData[nIndex] != '\t' && |
| pFontData[nIndex] != '\n' && |
| pFontData[nIndex] != ' ' ) |
| break; |
| } |
| nIndex--; |
| } |
| |
| if( nIndex < 1 || nIndex <= nEndAsciiIndex ) |
| throw FontException(); |
| |
| // nLength3 is the rest of the file - excluding any section headers |
| // nIndex now points to the first of the 512 '0' characters marking the |
| // fixed content portion |
| sal_Int32 nLength3 = nFontLen - nIndex; |
| for( it = aSections.begin(); it != aSections.end(); ++it ) |
| { |
| // special case: nIndex inside a section marker |
| if( nIndex >= (*it) && (*it)+6 > nIndex ) |
| nLength3 -= (*it)+6 - nIndex; |
| else if( *it >= nIndex ) |
| { |
| if( *it < nFontLen - 6 ) |
| nLength3 -= 6; |
| else // the last section 0x8003 is only 2 bytes after all |
| nLength3 -= (nFontLen - *it); |
| } |
| } |
| |
| // there may be whitespace to ignore before the 512 '0' |
| while( pFontData[nIndex] == '\r' || pFontData[nIndex] == '\n' ) |
| { |
| nIndex--; |
| for( it = aSections.begin(); it != aSections.end() && (nIndex < *it || nIndex > ((*it) + 5) ); ++it ) |
| ; |
| if( it != aSections.end() ) |
| { |
| nIndex = (*it)-1; |
| break; // this is surely a binary boundary, in ascii case it wouldn't matter |
| } |
| } |
| nEndBinaryIndex = nIndex; |
| |
| // search for beginning of binary section |
| nBeginBinaryIndex = nEndAsciiIndex; |
| do |
| { |
| nBeginBinaryIndex++; |
| for( it = aSections.begin(); it != aSections.end() && (nBeginBinaryIndex < *it || nBeginBinaryIndex > ((*it) + 5) ); ++it ) |
| ; |
| } while( nBeginBinaryIndex < nEndBinaryIndex && |
| ( pFontData[nBeginBinaryIndex] == '\r' || |
| pFontData[nBeginBinaryIndex] == '\n' || |
| it != aSections.end() ) ); |
| |
| // it seems to be vital to copy the exact whitespace between binary data |
| // and eexec, else a invalid font results. so make nEndAsciiIndex |
| // always immediate in front of nBeginBinaryIndex |
| nEndAsciiIndex = nBeginBinaryIndex-1; |
| for( it = aSections.begin(); it != aSections.end() && (nEndAsciiIndex < *it || nEndAsciiIndex > ((*it)+5)); ++it ) |
| ; |
| if( it != aSections.end() ) |
| nEndAsciiIndex = (*it)-1; |
| |
| nLength1 = nEndAsciiIndex+1; // including the last character |
| for( it = aSections.begin(); it != aSections.end() && *it < nEndAsciiIndex; ++it ) |
| nLength1 -= 6; // decrease by pfb section size |
| |
| // if the first four bytes are all ascii hex characters, then binary data |
| // has to be converted to real binary data |
| for( nIndex = 0; nIndex < 4 && |
| ( ( pFontData[ nBeginBinaryIndex+nIndex ] >= '0' && pFontData[ nBeginBinaryIndex+nIndex ] <= '9' ) || |
| ( pFontData[ nBeginBinaryIndex+nIndex ] >= 'a' && pFontData[ nBeginBinaryIndex+nIndex ] <= 'f' ) || |
| ( pFontData[ nBeginBinaryIndex+nIndex ] >= 'A' && pFontData[ nBeginBinaryIndex+nIndex ] <= 'F' ) |
| ); ++nIndex ) |
| ; |
| bool bConvertHexData = true; |
| if( nIndex < 4 ) |
| { |
| bConvertHexData = false; |
| nLength2 = nEndBinaryIndex - nBeginBinaryIndex + 1; // include the last byte |
| for( it = aSections.begin(); it != aSections.end(); ++it ) |
| if( *it > nBeginBinaryIndex && *it < nEndBinaryIndex ) |
| nLength2 -= 6; |
| } |
| else |
| { |
| // count the hex ascii characters to get nLength2 |
| nLength2 = 0; |
| int nNextSectionIndex = 0; |
| for( it = aSections.begin(); it != aSections.end() && *it < nBeginBinaryIndex; ++it ) |
| ; |
| if( it != aSections.end() ) |
| nNextSectionIndex = *it; |
| for( nIndex = nBeginBinaryIndex; nIndex <= nEndBinaryIndex; nIndex++ ) |
| { |
| if( nIndex == nNextSectionIndex ) |
| { |
| nIndex += 6; |
| ++it; |
| nNextSectionIndex = (it == aSections.end() ? 0 : *it ); |
| } |
| if( ( pFontData[ nIndex ] >= '0' && pFontData[ nIndex ] <= '9' ) || |
| ( pFontData[ nIndex ] >= 'a' && pFontData[ nIndex ] <= 'f' ) || |
| ( pFontData[ nIndex ] >= 'A' && pFontData[ nIndex ] <= 'F' ) ) |
| nLength2++; |
| } |
| DBG_ASSERT( !(nLength2 & 1), "uneven number of hex chars in binary pfa section" ); |
| nLength2 /= 2; |
| } |
| |
| // now we can actually write the font stream ! |
| #if OSL_DEBUG_LEVEL > 1 |
| emitComment( " PDFWriterImpl::emitEmbeddedFont" ); |
| #endif |
| OStringBuffer aLine( 512 ); |
| nStreamObject = createObject(); |
| if( !updateObject(nStreamObject)) |
| throw FontException(); |
| sal_Int32 nStreamLengthObject = createObject(); |
| aLine.append( nStreamObject ); |
| aLine.append( " 0 obj\n" |
| "<</Length " ); |
| aLine.append( nStreamLengthObject ); |
| aLine.append( " 0 R" |
| #ifndef DEBUG_DISABLE_PDFCOMPRESSION |
| "/Filter/FlateDecode" |
| #endif |
| "/Length1 " ); |
| aLine.append( nLength1 ); |
| aLine.append( " /Length2 " ); |
| aLine.append( nLength2 ); |
| aLine.append( " /Length3 "); |
| aLine.append( nLength3 ); |
| aLine.append( ">>\n" |
| "stream\n" ); |
| if( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) |
| throw FontException(); |
| |
| sal_uInt64 nBeginStreamPos = 0; |
| osl_getFilePos( m_aFile, &nBeginStreamPos ); |
| |
| beginCompression(); |
| checkAndEnableStreamEncryption( nStreamObject ); |
| |
| // write ascii section |
| if( aSections.begin() == aSections.end() ) |
| { |
| if( ! writeBuffer( pFontData, nEndAsciiIndex+1 ) ) |
| throw FontException(); |
| } |
| else |
| { |
| // first section always starts at 0 |
| it = aSections.begin(); |
| nIndex = (*it)+6; |
| ++it; |
| while( *it < nEndAsciiIndex ) |
| { |
| if( ! writeBuffer( pFontData+nIndex, (*it)-nIndex ) ) |
| throw FontException(); |
| nIndex = (*it)+6; |
| ++it; |
| } |
| // write partial last section |
| if( ! writeBuffer( pFontData+nIndex, nEndAsciiIndex-nIndex+1 ) ) |
| throw FontException(); |
| } |
| |
| // write binary section |
| if( ! bConvertHexData ) |
| { |
| if( aSections.begin() == aSections.end() ) |
| { |
| if( ! writeBuffer( pFontData+nBeginBinaryIndex, nFontLen-nBeginBinaryIndex ) ) |
| throw FontException(); |
| } |
| else |
| { |
| for( it = aSections.begin(); *it < nBeginBinaryIndex; ++it ) |
| ; |
| // write first partial section |
| if( ! writeBuffer( pFontData+nBeginBinaryIndex, (*it) - nBeginBinaryIndex ) ) |
| throw FontException(); |
| // write following sections |
| while( it != aSections.end() ) |
| { |
| nIndex = (*it)+6; |
| ++it; |
| if( nIndex < nFontLen ) // last section marker is usually the EOF which has only 2 bytes |
| { |
| sal_Int32 nSectionLen = (it == aSections.end()) ? nFontLen - nIndex : (*it) - nIndex; |
| if( ! writeBuffer( pFontData+nIndex, nSectionLen ) ) |
| throw FontException(); |
| } |
| } |
| } |
| } |
| else |
| { |
| boost::shared_array<unsigned char> pWriteBuffer( new unsigned char[ nLength2 ] ); |
| rtl_zeroMemory( pWriteBuffer.get(), nLength2 ); |
| int nWriteIndex = 0; |
| |
| int nNextSectionIndex = 0; |
| for( it = aSections.begin(); it != aSections.end() && *it < nBeginBinaryIndex; ++it ) |
| ; |
| if( it != aSections.end() ) |
| nNextSectionIndex = *it; |
| for( nIndex = nBeginBinaryIndex; nIndex <= nEndBinaryIndex; nIndex++ ) |
| { |
| if( nIndex == nNextSectionIndex ) |
| { |
| nIndex += 6; |
| ++it; |
| nNextSectionIndex = (it == aSections.end() ? nFontLen : *it ); |
| } |
| unsigned char cNibble = 0x80; |
| if( pFontData[ nIndex ] >= '0' && pFontData[ nIndex ] <= '9' ) |
| cNibble = pFontData[nIndex] - '0'; |
| else if( pFontData[ nIndex ] >= 'a' && pFontData[ nIndex ] <= 'f' ) |
| cNibble = pFontData[nIndex] - 'a' + 10; |
| else if( pFontData[ nIndex ] >= 'A' && pFontData[ nIndex ] <= 'F' ) |
| cNibble = pFontData[nIndex] - 'A' + 10; |
| if( cNibble != 0x80 ) |
| { |
| if( !(nWriteIndex & 1 ) ) |
| cNibble <<= 4; |
| pWriteBuffer.get()[ nWriteIndex/2 ] |= cNibble; |
| nWriteIndex++; |
| } |
| } |
| if( ! writeBuffer( pWriteBuffer.get(), nLength2 ) ) |
| throw FontException(); |
| if( aSections.empty() ) |
| { |
| if( ! writeBuffer( pFontData+nIndex, nFontLen-nIndex ) ) |
| throw FontException(); |
| } |
| else |
| { |
| // write rest of this section |
| if( nIndex < nNextSectionIndex ) |
| { |
| if( ! writeBuffer( pFontData+nIndex, nNextSectionIndex - nIndex ) ) |
| throw FontException(); |
| } |
| // write following sections |
| while( it != aSections.end() ) |
| { |
| nIndex = (*it)+6; |
| ++it; |
| if( nIndex < nFontLen ) // last section marker is usually the EOF which has only 2 bytes |
| { |
| sal_Int32 nSectionLen = (it == aSections.end()) ? nFontLen - nIndex : (*it) - nIndex; |
| if( ! writeBuffer( pFontData+nIndex, nSectionLen ) ) |
| throw FontException(); |
| } |
| } |
| } |
| } |
| endCompression(); |
| disableStreamEncryption(); |
| |
| |
| sal_uInt64 nEndStreamPos = 0; |
| osl_getFilePos( m_aFile, &nEndStreamPos ); |
| |
| // and finally close the stream |
| aLine.setLength( 0 ); |
| aLine.append( "\nendstream\nendobj\n\n" ); |
| if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) |
| throw FontException(); |
| |
| // write stream length object |
| aLine.setLength( 0 ); |
| if( ! updateObject( nStreamLengthObject ) ) |
| throw FontException(); |
| aLine.append( nStreamLengthObject ); |
| aLine.append( " 0 obj\n" ); |
| aLine.append( (sal_Int64)(nEndStreamPos-nBeginStreamPos ) ); |
| aLine.append( "\nendobj\n\n" ); |
| if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) |
| throw FontException(); |
| } |
| else |
| { |
| rtl::OStringBuffer aErrorComment( 256 ); |
| aErrorComment.append( "GetEmbedFontData failed for font \"" ); |
| aErrorComment.append( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) ); |
| aErrorComment.append( '\"' ); |
| if( pFont->GetSlant() == ITALIC_NORMAL ) |
| aErrorComment.append( " italic" ); |
| else if( pFont->GetSlant() == ITALIC_OBLIQUE ) |
| aErrorComment.append( " oblique" ); |
| aErrorComment.append( " weight=" ); |
| aErrorComment.append( sal_Int32(pFont->GetWeight()) ); |
| emitComment( aErrorComment.getStr() ); |
| } |
| |
| if( nStreamObject ) |
| // write font descriptor |
| nFontDescriptor = emitFontDescriptor( pFont, aInfo, 0, nStreamObject ); |
| |
| if( nFontDescriptor ) |
| { |
| if( pEncoding ) |
| nToUnicodeStream = createToUnicodeCMap( nEncoding, &aUnicodes[0], pUnicodesPerGlyph, pEncToUnicodeIndex, sizeof(nEncoding)/sizeof(nEncoding[0]) ); |
| |
| // write font object |
| sal_Int32 nObject = createObject(); |
| if( ! updateObject( nObject ) ) |
| throw FontException(); |
| |
| OStringBuffer aLine( 1024 ); |
| aLine.append( nObject ); |
| aLine.append( " 0 obj\n" |
| "<</Type/Font/Subtype/Type1/BaseFont/" ); |
| appendName( aInfo.m_aPSName, aLine ); |
| aLine.append( "\n" ); |
| if( !pFont->mbSymbolFlag && pEncoding == 0 ) |
| aLine.append( "/Encoding/WinAnsiEncoding\n" ); |
| if( nToUnicodeStream ) |
| { |
| aLine.append( "/ToUnicode " ); |
| aLine.append( nToUnicodeStream ); |
| aLine.append( " 0 R\n" ); |
| } |
| aLine.append( "/FirstChar 0 /LastChar 255\n" |
| "/Widths[" ); |
| for( int i = 0; i < 256; i++ ) |
| { |
| aLine.append( pWidths[i] ); |
| aLine.append( ((i&15) == 15) ? "\n" : " " ); |
| } |
| aLine.append( "]\n" |
| "/FontDescriptor " ); |
| aLine.append( nFontDescriptor ); |
| aLine.append( " 0 R>>\n" |
| "endobj\n\n" ); |
| if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) |
| throw FontException(); |
| |
| nFontObject = nObject; |
| |
| aRet[ rEmbed.m_nNormalFontID ] = nObject; |
| |
| // write additional encodings |
| for( std::list< EmbedEncoding >::iterator enc_it = rEmbed.m_aExtendedEncodings.begin(); enc_it != rEmbed.m_aExtendedEncodings.end(); ++enc_it ) |
| { |
| sal_Int32 aEncWidths[ 256 ]; |
| // emit encoding dict |
| sal_Int32 nEncObject = createObject(); |
| if( ! updateObject( nEncObject ) ) |
| throw FontException(); |
| |
| OutputDevice* pRef = getReferenceDevice(); |
| pRef->Push( PUSH_FONT | PUSH_MAPMODE ); |
| pRef->SetMapMode( MapMode( MAP_PIXEL ) ); |
| Font aFont( pFont->GetFamilyName(), pFont->GetStyleName(), Size( 0, 1000 ) ); |
| aFont.SetWeight( pFont->GetWeight() ); |
| aFont.SetItalic( pFont->GetSlant() ); |
| aFont.SetPitch( pFont->GetPitch() ); |
| pRef->SetFont( aFont ); |
| pRef->ImplNewFont(); |
| |
| aLine.setLength( 0 ); |
| aLine.append( nEncObject ); |
| aLine.append( " 0 obj\n" |
| "<</Type/Encoding/Differences[ 0\n" ); |
| int nEncoded = 0; |
| aUnicodes.clear(); |
| for( std::vector< EmbedCode >::iterator str_it = enc_it->m_aEncVector.begin(); str_it != enc_it->m_aEncVector.end(); ++str_it ) |
| { |
| String aStr( str_it->m_aUnicode ); |
| aEncWidths[nEncoded] = pRef->GetTextWidth( aStr ); |
| nEncodedCodes[nEncoded] = str_it->m_aUnicode; |
| nEncoding[nEncoded] = sal::static_int_cast<sal_uInt8>(nEncoded); |
| pEncToUnicodeIndex[nEncoded] = static_cast<sal_Int32>(aUnicodes.size()); |
| aUnicodes.push_back( nEncodedCodes[nEncoded] ); |
| pUnicodesPerGlyph[nEncoded] = 1; |
| |
| aLine.append( " /" ); |
| aLine.append( str_it->m_aName ); |
| if( !((++nEncoded) & 15) ) |
| aLine.append( "\n" ); |
| } |
| aLine.append( "]>>\n" |
| "endobj\n\n" ); |
| |
| pRef->Pop(); |
| |
| if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) |
| throw FontException(); |
| |
| nToUnicodeStream = createToUnicodeCMap( nEncoding, &aUnicodes[0], pUnicodesPerGlyph, pEncToUnicodeIndex, nEncoded ); |
| |
| nObject = createObject(); |
| if( ! updateObject( nObject ) ) |
| throw FontException(); |
| |
| aLine.setLength( 0 ); |
| aLine.append( nObject ); |
| aLine.append( " 0 obj\n" |
| "<</Type/Font/Subtype/Type1/BaseFont/" ); |
| appendName( aInfo.m_aPSName, aLine ); |
| aLine.append( "\n" ); |
| aLine.append( "/Encoding " ); |
| aLine.append( nEncObject ); |
| aLine.append( " 0 R\n" ); |
| if( nToUnicodeStream ) |
| { |
| aLine.append( "/ToUnicode " ); |
| aLine.append( nToUnicodeStream ); |
| aLine.append( " 0 R\n" ); |
| } |
| aLine.append( "/FirstChar 0\n" |
| "/LastChar " ); |
| aLine.append( (sal_Int32)(nEncoded-1) ); |
| aLine.append( "\n" |
| "/Widths[" ); |
| for( int i = 0; i < nEncoded; i++ ) |
| { |
| aLine.append( aEncWidths[i] ); |
| aLine.append( ((i&15) == 15) ? "\n" : " " ); |
| } |
| aLine.append( " ]\n" |
| "/FontDescriptor " ); |
| aLine.append( nFontDescriptor ); |
| aLine.append( " 0 R>>\n" |
| "endobj\n\n" ); |
| if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) |
| throw FontException(); |
| |
| aRet[ enc_it->m_nFontID ] = nObject; |
| } |
| } |
| } |
| catch( FontException& ) |
| { |
| // these do nothing in case there was no compression or encryption ongoing |
| endCompression(); |
| disableStreamEncryption(); |
| } |
| |
| if( pFontData ) |
| m_pReferenceDevice->mpGraphics->FreeEmbedFontData( pFontData, nFontLen ); |
| |
| return aRet; |
| } |
| |
| static void appendSubsetName( int nSubsetID, const OUString& rPSName, OStringBuffer& rBuffer ) |
| { |
| if( nSubsetID ) |
| { |
| for( int i = 0; i < 6; i++ ) |
| { |
| int nOffset = (nSubsetID % 26); |
| nSubsetID /= 26; |
| rBuffer.append( (sal_Char)('A'+nOffset) ); |
| } |
| rBuffer.append( '+' ); |
| } |
| appendName( rPSName, rBuffer ); |
| } |
| |
| sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8* pEncoding, |
| sal_Ucs* pUnicodes, |
| sal_Int32* pUnicodesPerGlyph, |
| sal_Int32* pEncToUnicodeIndex, |
| int nGlyphs ) |
| { |
| int nMapped = 0, n = 0; |
| for( n = 0; n < nGlyphs; n++ ) |
| if( pUnicodes[pEncToUnicodeIndex[n]] && pUnicodesPerGlyph[n] ) |
| nMapped++; |
| |
| if( nMapped == 0 ) |
| return 0; |
| |
| sal_Int32 nStream = createObject(); |
| CHECK_RETURN( updateObject( nStream ) ); |
| |
| OStringBuffer aContents( 1024 ); |
| aContents.append( |
| "/CIDInit/ProcSet findresource begin\n" |
| "12 dict begin\n" |
| "begincmap\n" |
| "/CIDSystemInfo<<\n" |
| "/Registry (Adobe)\n" |
| "/Ordering (UCS)\n" |
| "/Supplement 0\n" |
| ">> def\n" |
| "/CMapName/Adobe-Identity-UCS def\n" |
| "/CMapType 2 def\n" |
| "1 begincodespacerange\n" |
| "<00> <FF>\n" |
| "endcodespacerange\n" |
| ); |
| int nCount = 0; |
| for( n = 0; n < nGlyphs; n++ ) |
| { |
| if( pUnicodes[pEncToUnicodeIndex[n]] && pUnicodesPerGlyph[n] ) |
| { |
| if( (nCount % 100) == 0 ) |
| { |
| if( nCount ) |
| aContents.append( "endbfchar\n" ); |
| aContents.append( (sal_Int32)((nMapped-nCount > 100) ? 100 : nMapped-nCount ) ); |
| aContents.append( " beginbfchar\n" ); |
| } |
| aContents.append( '<' ); |
| appendHex( (sal_Int8)pEncoding[n], aContents ); |
| aContents.append( "> <" ); |
| // TODO: handle unicodes>U+FFFF |
| sal_Int32 nIndex = pEncToUnicodeIndex[n]; |
| for( sal_Int32 j = 0; j < pUnicodesPerGlyph[n]; j++ ) |
| { |
| appendHex( (sal_Int8)(pUnicodes[nIndex + j] / 256), aContents ); |
| appendHex( (sal_Int8)(pUnicodes[nIndex + j] & 255), aContents ); |
| } |
| aContents.append( ">\n" ); |
| nCount++; |
| } |
| } |
| aContents.append( "endbfchar\n" |
| "endcmap\n" |
| "CMapName currentdict /CMap defineresource pop\n" |
| "end\n" |
| "end\n" ); |
| #ifndef DEBUG_DISABLE_PDFCOMPRESSION |
| ZCodec* pCodec = new ZCodec( 0x4000, 0x4000 ); |
| SvMemoryStream aStream; |
| pCodec->BeginCompression(); |
| pCodec->Write( aStream, (const sal_uInt8*)aContents.getStr(), aContents.getLength() ); |
| pCodec->EndCompression(); |
| delete pCodec; |
| #endif |
| |
| #if OSL_DEBUG_LEVEL > 1 |
| emitComment( "PDFWriterImpl::createToUnicodeCMap" ); |
| #endif |
| OStringBuffer aLine( 40 ); |
| |
| aLine.append( nStream ); |
| aLine.append( " 0 obj\n<</Length " ); |
| #ifndef DEBUG_DISABLE_PDFCOMPRESSION |
| sal_Int32 nLen = (sal_Int32)aStream.Tell(); |
| aStream.Seek( 0 ); |
| aLine.append( nLen ); |
| aLine.append( "/Filter/FlateDecode" ); |
| #else |
| aLine.append( aContents.getLength() ); |
| #endif |
| aLine.append( ">>\nstream\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| checkAndEnableStreamEncryption( nStream ); |
| #ifndef DEBUG_DISABLE_PDFCOMPRESSION |
| CHECK_RETURN( writeBuffer( aStream.GetData(), nLen ) ); |
| #else |
| CHECK_RETURN( writeBuffer( aContents.getStr(), aContents.getLength() ) ); |
| #endif |
| disableStreamEncryption(); |
| aLine.setLength( 0 ); |
| aLine.append( "\nendstream\n" |
| "endobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| return nStream; |
| } |
| |
| sal_Int32 PDFWriterImpl::emitFontDescriptor( const ImplFontData* pFont, FontSubsetInfo& rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream ) |
| { |
| OStringBuffer aLine( 1024 ); |
| // get font flags, see PDF reference 1.4 p. 358 |
| // possibly characters outside Adobe standard encoding |
| // so set Symbolic flag |
| sal_Int32 nFontFlags = (1<<2); |
| if( pFont->GetSlant() == ITALIC_NORMAL || pFont->GetSlant() == ITALIC_OBLIQUE ) |
| nFontFlags |= (1 << 6); |
| if( pFont->GetPitch() == PITCH_FIXED ) |
| nFontFlags |= 1; |
| if( pFont->GetFamilyType() == FAMILY_SCRIPT ) |
| nFontFlags |= (1 << 3); |
| else if( pFont->GetFamilyType() == FAMILY_ROMAN ) |
| nFontFlags |= (1 << 1); |
| |
| sal_Int32 nFontDescriptor = createObject(); |
| CHECK_RETURN( updateObject( nFontDescriptor ) ); |
| aLine.setLength( 0 ); |
| aLine.append( nFontDescriptor ); |
| aLine.append( " 0 obj\n" |
| "<</Type/FontDescriptor/FontName/" ); |
| appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine ); |
| aLine.append( "\n" |
| "/Flags " ); |
| aLine.append( nFontFlags ); |
| aLine.append( "\n" |
| "/FontBBox[" ); |
| // note: Top and Bottom are reversed in VCL and PDF rectangles |
| aLine.append( (sal_Int32)rInfo.m_aFontBBox.TopLeft().X() ); |
| aLine.append( ' ' ); |
| aLine.append( (sal_Int32)rInfo.m_aFontBBox.TopLeft().Y() ); |
| aLine.append( ' ' ); |
| aLine.append( (sal_Int32)rInfo.m_aFontBBox.BottomRight().X() ); |
| aLine.append( ' ' ); |
| aLine.append( (sal_Int32)(rInfo.m_aFontBBox.BottomRight().Y()+1) ); |
| aLine.append( "]/ItalicAngle " ); |
| if( pFont->GetSlant() == ITALIC_OBLIQUE || pFont->GetSlant() == ITALIC_NORMAL ) |
| aLine.append( "-30" ); |
| else |
| aLine.append( "0" ); |
| aLine.append( "\n" |
| "/Ascent " ); |
| aLine.append( (sal_Int32)rInfo.m_nAscent ); |
| aLine.append( "\n" |
| "/Descent " ); |
| aLine.append( (sal_Int32)-rInfo.m_nDescent ); |
| aLine.append( "\n" |
| "/CapHeight " ); |
| aLine.append( (sal_Int32)rInfo.m_nCapHeight ); |
| // According to PDF reference 1.4 StemV is required |
| // seems a tad strange to me, but well ... |
| aLine.append( "\n" |
| "/StemV 80\n" ); |
| if( nFontStream ) |
| { |
| aLine.append( "/FontFile" ); |
| switch( rInfo.m_nFontType ) |
| { |
| case FontSubsetInfo::SFNT_TTF: |
| aLine.append( '2' ); |
| break; |
| case FontSubsetInfo::TYPE1_PFA: |
| case FontSubsetInfo::TYPE1_PFB: |
| case FontSubsetInfo::ANY_TYPE1: |
| break; |
| default: |
| DBG_ERROR( "unknown fonttype in PDF font descriptor" ); |
| return 0; |
| } |
| aLine.append( ' ' ); |
| aLine.append( nFontStream ); |
| aLine.append( " 0 R\n" ); |
| } |
| aLine.append( ">>\n" |
| "endobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| return nFontDescriptor; |
| } |
| |
| void PDFWriterImpl::appendBuiltinFontsToDict( OStringBuffer& rDict ) const |
| { |
| for( std::map< sal_Int32, sal_Int32 >::const_iterator it = |
| m_aBuiltinFontToObjectMap.begin(); it != m_aBuiltinFontToObjectMap.end(); ++it ) |
| { |
| rDict.append( m_aBuiltinFonts[it->first].getNameObject() ); |
| rDict.append( ' ' ); |
| rDict.append( it->second ); |
| rDict.append( " 0 R" ); |
| } |
| } |
| |
| bool PDFWriterImpl::emitFonts() |
| { |
| if( ! m_pReferenceDevice->ImplGetGraphics() ) |
| return false; |
| |
| OStringBuffer aLine( 1024 ); |
| |
| std::map< sal_Int32, sal_Int32 > aFontIDToObject; |
| |
| OUString aTmpName; |
| osl_createTempFile( NULL, NULL, &aTmpName.pData ); |
| for( FontSubsetData::iterator it = m_aSubsets.begin(); it != m_aSubsets.end(); ++it ) |
| { |
| for( FontEmitList::iterator lit = it->second.m_aSubsets.begin(); lit != it->second.m_aSubsets.end(); ++lit ) |
| { |
| sal_GlyphId aGlyphIds[ 256 ]; |
| sal_Int32 pWidths[ 256 ]; |
| sal_uInt8 pEncoding[ 256 ]; |
| sal_Int32 pEncToUnicodeIndex[ 256 ]; |
| sal_Int32 pUnicodesPerGlyph[ 256 ]; |
| std::vector<sal_Ucs> aUnicodes; |
| aUnicodes.reserve( 256 ); |
| int nGlyphs = 1; |
| // fill arrays and prepare encoding index map |
| sal_Int32 nToUnicodeStream = 0; |
| |
| rtl_zeroMemory( aGlyphIds, sizeof( aGlyphIds ) ); |
| rtl_zeroMemory( pEncoding, sizeof( pEncoding ) ); |
| rtl_zeroMemory( pUnicodesPerGlyph, sizeof( pUnicodesPerGlyph ) ); |
| rtl_zeroMemory( pEncToUnicodeIndex, sizeof( pEncToUnicodeIndex ) ); |
| for( FontEmitMapping::iterator fit = lit->m_aMapping.begin(); fit != lit->m_aMapping.end();++fit ) |
| { |
| sal_uInt8 nEnc = fit->second.getGlyphId(); |
| |
| DBG_ASSERT( aGlyphIds[nEnc] == 0 && pEncoding[nEnc] == 0, "duplicate glyph" ); |
| DBG_ASSERT( nEnc <= lit->m_aMapping.size(), "invalid glyph encoding" ); |
| |
| aGlyphIds[ nEnc ] = fit->first; |
| pEncoding[ nEnc ] = nEnc; |
| pEncToUnicodeIndex[ nEnc ] = static_cast<sal_Int32>(aUnicodes.size()); |
| pUnicodesPerGlyph[ nEnc ] = fit->second.countCodes(); |
| for( sal_Int32 n = 0; n < pUnicodesPerGlyph[ nEnc ]; n++ ) |
| aUnicodes.push_back( fit->second.getCode( n ) ); |
| if( fit->second.getCode(0) ) |
| nToUnicodeStream = 1; |
| if( nGlyphs < 256 ) |
| nGlyphs++; |
| else |
| { |
| DBG_ERROR( "too many glyphs for subset" ); |
| } |
| } |
| FontSubsetInfo aSubsetInfo; |
| if( m_pReferenceDevice->mpGraphics->CreateFontSubset( aTmpName, it->first, aGlyphIds, pEncoding, pWidths, nGlyphs, aSubsetInfo ) ) |
| { |
| // create font stream |
| oslFileHandle aFontFile; |
| CHECK_RETURN( (osl_File_E_None == osl_openFile( aTmpName.pData, &aFontFile, osl_File_OpenFlag_Read ) ) ); |
| // get file size |
| sal_uInt64 nLength1; |
| CHECK_RETURN( (osl_File_E_None == osl_setFilePos( aFontFile, osl_Pos_End, 0 ) ) ); |
| CHECK_RETURN( (osl_File_E_None == osl_getFilePos( aFontFile, &nLength1 ) ) ); |
| CHECK_RETURN( (osl_File_E_None == osl_setFilePos( aFontFile, osl_Pos_Absolut, 0 ) ) ); |
| |
| #if OSL_DEBUG_LEVEL > 1 |
| emitComment( "PDFWriterImpl::emitFonts" ); |
| #endif |
| sal_Int32 nFontStream = createObject(); |
| sal_Int32 nStreamLengthObject = createObject(); |
| CHECK_RETURN( updateObject( nFontStream ) ); |
| aLine.setLength( 0 ); |
| aLine.append( nFontStream ); |
| aLine.append( " 0 obj\n" |
| "<</Length " ); |
| aLine.append( (sal_Int32)nStreamLengthObject ); |
| aLine.append( " 0 R" |
| #ifndef DEBUG_DISABLE_PDFCOMPRESSION |
| "/Filter/FlateDecode" |
| #endif |
| "/Length1 " ); |
| |
| sal_uInt64 nStartPos = 0; |
| if( aSubsetInfo.m_nFontType == FontSubsetInfo::SFNT_TTF ) |
| { |
| aLine.append( (sal_Int32)nLength1 ); |
| |
| aLine.append( ">>\n" |
| "stream\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nStartPos ) ) ); |
| |
| // copy font file |
| beginCompression(); |
| checkAndEnableStreamEncryption( nFontStream ); |
| sal_Bool bEOF = sal_False; |
| do |
| { |
| char buf[8192]; |
| sal_uInt64 nRead; |
| CHECK_RETURN( (osl_File_E_None == osl_readFile( aFontFile, buf, sizeof( buf ), &nRead ) ) ); |
| CHECK_RETURN( writeBuffer( buf, nRead ) ); |
| CHECK_RETURN( (osl_File_E_None == osl_isEndOfFile( aFontFile, &bEOF ) ) ); |
| } while( ! bEOF ); |
| } |
| else if( (aSubsetInfo.m_nFontType & FontSubsetInfo::CFF_FONT) != 0 ) |
| { |
| // TODO: implement |
| DBG_ERROR( "PDFWriterImpl does not support CFF-font subsets yet!" ); |
| } |
| else if( (aSubsetInfo.m_nFontType & FontSubsetInfo::TYPE1_PFB) != 0 ) // TODO: also support PFA? |
| { |
| boost::shared_array<unsigned char> pBuffer( new unsigned char[ nLength1 ] ); |
| |
| sal_uInt64 nBytesRead = 0; |
| CHECK_RETURN( (osl_File_E_None == osl_readFile( aFontFile, pBuffer.get(), nLength1, &nBytesRead ) ) ); |
| DBG_ASSERT( nBytesRead==nLength1, "PDF-FontSubset read incomplete!" ); |
| CHECK_RETURN( (osl_File_E_None == osl_setFilePos( aFontFile, osl_Pos_Absolut, 0 ) ) ); |
| // get the PFB-segment lengths |
| ThreeInts aSegmentLengths = {0,0,0}; |
| getPfbSegmentLengths( pBuffer.get(), (int)nBytesRead, aSegmentLengths ); |
| // the lengths below are mandatory for PDF-exported Type1 fonts |
| // because the PFB segment headers get stripped! WhyOhWhy. |
| aLine.append( (sal_Int32)aSegmentLengths[0] ); |
| aLine.append( "/Length2 " ); |
| aLine.append( (sal_Int32)aSegmentLengths[1] ); |
| aLine.append( "/Length3 " ); |
| aLine.append( (sal_Int32)aSegmentLengths[2] ); |
| |
| aLine.append( ">>\n" |
| "stream\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nStartPos ) ) ); |
| |
| // emit PFB-sections without section headers |
| beginCompression(); |
| checkAndEnableStreamEncryption( nFontStream ); |
| CHECK_RETURN( writeBuffer( &pBuffer[6], aSegmentLengths[0] ) ); |
| CHECK_RETURN( writeBuffer( &pBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ); |
| CHECK_RETURN( writeBuffer( &pBuffer[18] + aSegmentLengths[0] + aSegmentLengths[1], aSegmentLengths[2] ) ); |
| } |
| else |
| { |
| fprintf( stderr, "PDF: CreateFontSubset result in not yet supported format=%d\n",aSubsetInfo.m_nFontType); |
| aLine.append( "0 >>\nstream\n" ); |
| } |
| |
| endCompression(); |
| disableStreamEncryption(); |
| // close the file |
| osl_closeFile( aFontFile ); |
| |
| sal_uInt64 nEndPos = 0; |
| CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nEndPos ) ) ); |
| // end the stream |
| aLine.setLength( 0 ); |
| aLine.append( "\nendstream\nendobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| // emit stream length object |
| CHECK_RETURN( updateObject( nStreamLengthObject ) ); |
| aLine.setLength( 0 ); |
| aLine.append( nStreamLengthObject ); |
| aLine.append( " 0 obj\n" ); |
| aLine.append( (sal_Int64)(nEndPos-nStartPos) ); |
| aLine.append( "\nendobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| // write font descriptor |
| sal_Int32 nFontDescriptor = emitFontDescriptor( it->first, aSubsetInfo, lit->m_nFontID, nFontStream ); |
| |
| if( nToUnicodeStream ) |
| nToUnicodeStream = createToUnicodeCMap( pEncoding, &aUnicodes[0], pUnicodesPerGlyph, pEncToUnicodeIndex, nGlyphs ); |
| |
| sal_Int32 nFontObject = createObject(); |
| CHECK_RETURN( updateObject( nFontObject ) ); |
| aLine.setLength( 0 ); |
| aLine.append( nFontObject ); |
| |
| aLine.append( " 0 obj\n" ); |
| aLine.append( ((aSubsetInfo.m_nFontType & FontSubsetInfo::ANY_TYPE1) != 0) ? |
| "<</Type/Font/Subtype/Type1/BaseFont/" : |
| "<</Type/Font/Subtype/TrueType/BaseFont/" ); |
| appendSubsetName( lit->m_nFontID, aSubsetInfo.m_aPSName, aLine ); |
| aLine.append( "\n" |
| "/FirstChar 0\n" |
| "/LastChar " ); |
| aLine.append( (sal_Int32)(nGlyphs-1) ); |
| aLine.append( "\n" |
| "/Widths[" ); |
| for( int i = 0; i < nGlyphs; i++ ) |
| { |
| aLine.append( pWidths[ i ] ); |
| aLine.append( ((i & 15) == 15) ? "\n" : " " ); |
| } |
| aLine.append( "]\n" |
| "/FontDescriptor " ); |
| aLine.append( nFontDescriptor ); |
| aLine.append( " 0 R\n" ); |
| if( nToUnicodeStream ) |
| { |
| aLine.append( "/ToUnicode " ); |
| aLine.append( nToUnicodeStream ); |
| aLine.append( " 0 R\n" ); |
| } |
| aLine.append( ">>\n" |
| "endobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| aFontIDToObject[ lit->m_nFontID ] = nFontObject; |
| } |
| else |
| { |
| const ImplFontData* pFont = it->first; |
| rtl::OStringBuffer aErrorComment( 256 ); |
| aErrorComment.append( "CreateFontSubset failed for font \"" ); |
| aErrorComment.append( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) ); |
| aErrorComment.append( '\"' ); |
| if( pFont->GetSlant() == ITALIC_NORMAL ) |
| aErrorComment.append( " italic" ); |
| else if( pFont->GetSlant() == ITALIC_OBLIQUE ) |
| aErrorComment.append( " oblique" ); |
| aErrorComment.append( " weight=" ); |
| aErrorComment.append( sal_Int32(pFont->GetWeight()) ); |
| emitComment( aErrorComment.getStr() ); |
| } |
| } |
| } |
| osl_removeFile( aTmpName.pData ); |
| |
| // emit embedded fonts |
| for( FontEmbedData::iterator eit = m_aEmbeddedFonts.begin(); eit != m_aEmbeddedFonts.end(); ++eit ) |
| { |
| std::map< sal_Int32, sal_Int32 > aObjects = emitEmbeddedFont( eit->first, eit->second ); |
| for( std::map< sal_Int32, sal_Int32 >::iterator fit = aObjects.begin(); fit != aObjects.end(); ++fit ) |
| { |
| CHECK_RETURN( fit->second ); |
| aFontIDToObject[ fit->first ] = fit->second; |
| } |
| } |
| |
| // emit system fonts |
| for( FontEmbedData::iterator sit = m_aSystemFonts.begin(); sit != m_aSystemFonts.end(); ++sit ) |
| { |
| std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( sit->first, sit->second ); |
| for( std::map< sal_Int32, sal_Int32 >::iterator fit = aObjects.begin(); fit != aObjects.end(); ++fit ) |
| { |
| CHECK_RETURN( fit->second ); |
| aFontIDToObject[ fit->first ] = fit->second; |
| } |
| } |
| |
| OStringBuffer aFontDict( 1024 ); |
| aFontDict.append( getFontDictObject() ); |
| aFontDict.append( " 0 obj\n" |
| "<<" ); |
| int ni = 0; |
| for( std::map< sal_Int32, sal_Int32 >::iterator mit = aFontIDToObject.begin(); mit != aFontIDToObject.end(); ++mit ) |
| { |
| aFontDict.append( "/F" ); |
| aFontDict.append( mit->first ); |
| aFontDict.append( ' ' ); |
| aFontDict.append( mit->second ); |
| aFontDict.append( " 0 R" ); |
| if( ((++ni) & 7) == 0 ) |
| aFontDict.append( '\n' ); |
| } |
| // emit builtin font for widget apperances / variable text |
| for( std::map< sal_Int32, sal_Int32 >::iterator it = m_aBuiltinFontToObjectMap.begin(); |
| it != m_aBuiltinFontToObjectMap.end(); ++it ) |
| { |
| ImplPdfBuiltinFontData aData(m_aBuiltinFonts[it->first]); |
| it->second = emitBuiltinFont( &aData, it->second ); |
| } |
| appendBuiltinFontsToDict( aFontDict ); |
| aFontDict.append( "\n>>\nendobj\n\n" ); |
| |
| CHECK_RETURN( updateObject( getFontDictObject() ) ); |
| CHECK_RETURN( writeBuffer( aFontDict.getStr(), aFontDict.getLength() ) ); |
| return true; |
| } |
| |
| sal_Int32 PDFWriterImpl::emitResources() |
| { |
| // emit shadings |
| if( ! m_aGradients.empty() ) |
| CHECK_RETURN( emitGradients() ); |
| // emit tilings |
| if( ! m_aTilings.empty() ) |
| CHECK_RETURN( emitTilings() ); |
| |
| // emit font dict |
| CHECK_RETURN( emitFonts() ); |
| |
| // emit Resource dict |
| OStringBuffer aLine( 512 ); |
| sal_Int32 nResourceDict = getResourceDictObj(); |
| CHECK_RETURN( updateObject( nResourceDict ) ); |
| aLine.setLength( 0 ); |
| aLine.append( nResourceDict ); |
| aLine.append( " 0 obj\n" ); |
| m_aGlobalResourceDict.append( aLine, getFontDictObject() ); |
| aLine.append( "endobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| return nResourceDict; |
| } |
| |
| sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts, |
| sal_Int32 nItemLevel, |
| sal_Int32 nCurrentItemId ) |
| { |
| /* The /Count number of an item is |
| positive: the number of visible subitems |
| negative: the negative number of subitems that will become visible if |
| the item gets opened |
| see PDF ref 1.4 p 478 |
| */ |
| |
| sal_Int32 nCount = 0; |
| |
| if( m_aContext.OpenBookmarkLevels < 0 || // all levels arevisible |
| m_aContext.OpenBookmarkLevels >= nItemLevel // this level is visible |
| ) |
| { |
| PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ]; |
| sal_Int32 nChildren = rItem.m_aChildren.size(); |
| for( sal_Int32 i = 0; i < nChildren; i++ ) |
| nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] ); |
| rCounts[nCurrentItemId] = nCount; |
| // return 1 (this item) + visible sub items |
| if( nCount < 0 ) |
| nCount = 0; |
| nCount++; |
| } |
| else |
| { |
| // this bookmark level is invisible |
| PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ]; |
| sal_Int32 nChildren = rItem.m_aChildren.size(); |
| rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size()); |
| for( sal_Int32 i = 0; i < nChildren; i++ ) |
| updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] ); |
| nCount = -1; |
| } |
| |
| return nCount; |
| } |
| |
| sal_Int32 PDFWriterImpl::emitOutline() |
| { |
| int i, nItems = m_aOutline.size(); |
| |
| // do we have an outline at all ? |
| if( nItems < 2 ) |
| return 0; |
| |
| // reserve object numbers for all outline items |
| for( i = 0; i < nItems; ++i ) |
| m_aOutline[i].m_nObject = createObject(); |
| |
| // update all parent, next and prev object ids |
| for( i = 0; i < nItems; ++i ) |
| { |
| PDFOutlineEntry& rItem = m_aOutline[i]; |
| int nChildren = rItem.m_aChildren.size(); |
| |
| if( nChildren ) |
| { |
| for( int n = 0; n < nChildren; ++n ) |
| { |
| PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ]; |
| |
| rChild.m_nParentObject = rItem.m_nObject; |
| rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0; |
| rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0; |
| } |
| |
| } |
| } |
| |
| // calculate Count entries for all items |
| std::vector< sal_Int32 > aCounts( nItems ); |
| updateOutlineItemCount( aCounts, 0, 0 ); |
| |
| // emit hierarchy |
| for( i = 0; i < nItems; ++i ) |
| { |
| PDFOutlineEntry& rItem = m_aOutline[i]; |
| OStringBuffer aLine( 1024 ); |
| |
| CHECK_RETURN( updateObject( rItem.m_nObject ) ); |
| aLine.append( rItem.m_nObject ); |
| aLine.append( " 0 obj\n" ); |
| aLine.append( "<<" ); |
| // number of visible children (all levels) |
| if( i > 0 || aCounts[0] > 0 ) |
| { |
| aLine.append( "/Count " ); |
| aLine.append( aCounts[i] ); |
| } |
| if( ! rItem.m_aChildren.empty() ) |
| { |
| // children list: First, Last |
| aLine.append( "/First " ); |
| aLine.append( m_aOutline[rItem.m_aChildren.front()].m_nObject ); |
| aLine.append( " 0 R/Last " ); |
| aLine.append( m_aOutline[rItem.m_aChildren.back()].m_nObject ); |
| aLine.append( " 0 R\n" ); |
| } |
| if( i > 0 ) |
| { |
| // Title, Dest, Parent, Prev, Next |
| aLine.append( "/Title" ); |
| appendUnicodeTextStringEncrypt( rItem.m_aTitle, rItem.m_nObject, aLine ); |
| aLine.append( "\n" ); |
| // Dest is not required |
| if( rItem.m_nDestID >= 0 && rItem.m_nDestID < (sal_Int32)m_aDests.size() ) |
| { |
| aLine.append( "/Dest" ); |
| appendDest( rItem.m_nDestID, aLine ); |
| } |
| aLine.append( "/Parent " ); |
| aLine.append( rItem.m_nParentObject ); |
| aLine.append( " 0 R" ); |
| if( rItem.m_nPrevObject ) |
| { |
| aLine.append( "/Prev " ); |
| aLine.append( rItem.m_nPrevObject ); |
| aLine.append( " 0 R" ); |
| } |
| if( rItem.m_nNextObject ) |
| { |
| aLine.append( "/Next " ); |
| aLine.append( rItem.m_nNextObject ); |
| aLine.append( " 0 R" ); |
| } |
| } |
| aLine.append( ">>\nendobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| } |
| |
| return m_aOutline[0].m_nObject; |
| } |
| |
| #undef CHECK_RETURN |
| #define CHECK_RETURN( x ) if( !x ) return false |
| |
| bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer ) |
| { |
| if( nDestID < 0 || nDestID >= (sal_Int32)m_aDests.size() ) |
| { |
| #if OSL_DEBUG_LEVEL > 1 |
| fprintf( stderr, "ERROR: invalid dest %d requested\n", (int)nDestID ); |
| #endif |
| return false; |
| } |
| |
| |
| const PDFDest& rDest = m_aDests[ nDestID ]; |
| const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ]; |
| |
| rBuffer.append( '[' ); |
| rBuffer.append( rDestPage.m_nPageObject ); |
| rBuffer.append( " 0 R" ); |
| |
| switch( rDest.m_eType ) |
| { |
| case PDFWriter::XYZ: |
| default: |
| rBuffer.append( "/XYZ " ); |
| appendFixedInt( rDest.m_aRect.Left(), rBuffer ); |
| rBuffer.append( ' ' ); |
| appendFixedInt( rDest.m_aRect.Bottom(), rBuffer ); |
| rBuffer.append( " 0" ); |
| break; |
| case PDFWriter::Fit: |
| rBuffer.append( "/Fit" ); |
| break; |
| case PDFWriter::FitRectangle: |
| rBuffer.append( "/FitR " ); |
| appendFixedInt( rDest.m_aRect.Left(), rBuffer ); |
| rBuffer.append( ' ' ); |
| appendFixedInt( rDest.m_aRect.Top(), rBuffer ); |
| rBuffer.append( ' ' ); |
| appendFixedInt( rDest.m_aRect.Right(), rBuffer ); |
| rBuffer.append( ' ' ); |
| appendFixedInt( rDest.m_aRect.Bottom(), rBuffer ); |
| break; |
| case PDFWriter::FitHorizontal: |
| rBuffer.append( "/FitH " ); |
| appendFixedInt( rDest.m_aRect.Bottom(), rBuffer ); |
| break; |
| case PDFWriter::FitVertical: |
| rBuffer.append( "/FitV " ); |
| appendFixedInt( rDest.m_aRect.Left(), rBuffer ); |
| break; |
| case PDFWriter::FitPageBoundingBox: |
| rBuffer.append( "/FitB" ); |
| break; |
| case PDFWriter::FitPageBoundingBoxHorizontal: |
| rBuffer.append( "/FitBH " ); |
| appendFixedInt( rDest.m_aRect.Bottom(), rBuffer ); |
| break; |
| case PDFWriter::FitPageBoundingBoxVertical: |
| rBuffer.append( "/FitBV " ); |
| appendFixedInt( rDest.m_aRect.Left(), rBuffer ); |
| break; |
| } |
| rBuffer.append( ']' ); |
| |
| return true; |
| } |
| |
| bool PDFWriterImpl::emitLinkAnnotations() |
| { |
| int nAnnots = m_aLinks.size(); |
| for( int i = 0; i < nAnnots; i++ ) |
| { |
| const PDFLink& rLink = m_aLinks[i]; |
| if( ! updateObject( rLink.m_nObject ) ) |
| continue; |
| |
| OStringBuffer aLine( 1024 ); |
| aLine.append( rLink.m_nObject ); |
| aLine.append( " 0 obj\n" ); |
| //i59651 key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should' |
| // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3 |
| aLine.append( "<</Type/Annot" ); |
| if( m_bIsPDF_A1 ) |
| aLine.append( "/F 4" ); |
| aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" ); |
| |
| appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle |
| aLine.append( ' ' ); |
| appendFixedInt( rLink.m_aRect.Top(), aLine ); |
| aLine.append( ' ' ); |
| appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle |
| aLine.append( ' ' ); |
| appendFixedInt( rLink.m_aRect.Bottom(), aLine ); |
| aLine.append( "]" ); |
| if( rLink.m_nDest >= 0 ) |
| { |
| aLine.append( "/Dest" ); |
| appendDest( rLink.m_nDest, aLine ); |
| } |
| else |
| { |
| /*--->i56629 |
| destination is external to the document, so |
| we check in the following sequence: |
| |
| if target type is neither .pdf, nor .od[tpgs], then |
| check if relative or absolute and act accordingly (use URI or 'launch application' as requested) |
| end processing |
| else if target is .od[tpgs]: then |
| if conversion of type from od[tpgs] to pdf is requested, convert it and this becomes the new target file |
| processing continue |
| |
| if (new)target is .pdf : then |
| if GotToR is requested, then |
| convert the target in GoToR where the fragment of the URI is |
| considered the named destination in the target file, set relative or absolute as requested |
| else strip the fragment from URL and then set URI or 'launch application' as requested |
| */ |
| // |
| // FIXME: check if the decode mechanisms for URL processing throughout this implementation |
| // are the correct one!! |
| // |
| // extract target file type |
| INetURLObject aDocumentURL( m_aContext.BaseURL ); |
| INetURLObject aTargetURL( rLink.m_aURL ); |
| sal_Int32 nChangeFileExtensionToPDF = 0; |
| sal_Int32 nSetGoToRMode = 0; |
| sal_Bool bTargetHasPDFExtension = sal_False; |
| INetProtocol eTargetProtocol = aTargetURL.GetProtocol(); |
| sal_Bool bIsUNCPath = sal_False; |
| // check if the protocol is a known one, or if there is no protocol at all (on target only) |
| // if there is no protocol, make the target relative to the current document directory |
| // getting the needed URL information from the current document path |
| if( eTargetProtocol == INET_PROT_NOT_VALID ) |
| { |
| if( rLink.m_aURL.getLength() > 4 && rLink.m_aURL.compareToAscii( "\\\\\\\\", 4 ) == 0) |
| { |
| bIsUNCPath = sal_True; |
| } |
| else |
| { |
| INetURLObject aNewBase( aDocumentURL );//duplicate document URL |
| aNewBase.removeSegment(); //remove last segment from it, obtaining the base URL of the |
| //target document |
| aNewBase.insertName( rLink.m_aURL ); |
| aTargetURL = aNewBase;//reassign the new target URL |
| //recompute the target protocol, with the new URL |
| //normal URL processing resumes |
| eTargetProtocol = aTargetURL.GetProtocol(); |
| } |
| } |
| |
| rtl::OUString aFileExtension = aTargetURL.GetFileExtension(); |
| |
| // Check if the URL ends in '/': if yes it's a directory, |
| // it will be forced to a URI link. |
| // possibly a malformed URI, leave it as it is, force as URI |
| if( aTargetURL.hasFinalSlash() ) |
| m_aContext.DefaultLinkAction = PDFWriter::URIAction; |
| |
| if( aFileExtension.getLength() > 0 ) |
| { |
| if( m_aContext.ConvertOOoTargetToPDFTarget ) |
| { |
| //examine the file type (.odm .odt. .odp, odg, ods) |
| if( aFileExtension.equalsIgnoreAsciiCase(rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "odm" ) ) ) ) |
| nChangeFileExtensionToPDF++; |
| if( aFileExtension.equalsIgnoreAsciiCase(rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "odt" ) ) ) ) |
| nChangeFileExtensionToPDF++; |
| else if( aFileExtension.equalsIgnoreAsciiCase(rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "odp" ) ) ) ) |
| nChangeFileExtensionToPDF++; |
| else if( aFileExtension.equalsIgnoreAsciiCase(rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "odg" ) ) ) ) |
| nChangeFileExtensionToPDF++; |
| else if( aFileExtension.equalsIgnoreAsciiCase(rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "ods" ) ) ) ) |
| nChangeFileExtensionToPDF++; |
| if( nChangeFileExtensionToPDF ) |
| aTargetURL.setExtension(rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "pdf" ) ) ); |
| } |
| //check if extension is pdf, see if GoToR should be forced |
| bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase(rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "pdf" ) ) ); |
| if( m_aContext.ForcePDFAction && bTargetHasPDFExtension ) |
| nSetGoToRMode++; |
| } |
| //prepare the URL, if relative or not |
| INetProtocol eBaseProtocol = aDocumentURL.GetProtocol(); |
| //queue the string common to all types of actions |
| aLine.append( "/A<</Type/Action/S"); |
| if( bIsUNCPath ) // handle Win UNC paths |
| { |
| aLine.append( "/Launch/Win<</F" ); |
| // INetURLObject is not good with UNC paths, use original path |
| appendLiteralStringEncrypt( rLink.m_aURL, rLink.m_nObject, aLine, osl_getThreadTextEncoding() ); |
| aLine.append( ">>" ); |
| } |
| else |
| { |
| bool bSetRelative = false; |
| bool bFileSpec = false; |
| //check if relative file link is requested and if the protocol is 'file://' |
| if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INET_PROT_FILE ) |
| bSetRelative = true; |
| |
| rtl::OUString aFragment = aTargetURL.GetMark( INetURLObject::NO_DECODE /*DECODE_WITH_CHARSET*/ ); //fragment as is, |
| if( nSetGoToRMode == 0 ) |
| { |
| switch( m_aContext.DefaultLinkAction ) |
| { |
| default: |
| case PDFWriter::URIAction : |
| case PDFWriter::URIActionDestination : |
| aLine.append( "/URI/URI" ); |
| break; |
| case PDFWriter::LaunchAction: |
| // now: |
| // if a launch action is requested and the hyperlink target has a fragment |
| // and the target file does not have a pdf extension, or it's not a 'file:://' protocol |
| // then force the uri action on it |
| // This code will permit the correct opening of application on web pages, the one that |
| // normally have fragments (but I may be wrong...) |
| // and will force the use of URI when the protocol is not file:// |
| if( (aFragment.getLength() > 0 && !bTargetHasPDFExtension) || |
| eTargetProtocol != INET_PROT_FILE ) |
| aLine.append( "/URI/URI" ); |
| else |
| { |
| aLine.append( "/Launch/F" ); |
| bFileSpec = true; |
| } |
| break; |
| } |
| } |
| //fragment are encoded in the same way as in the named destination processing |
| if( nSetGoToRMode ) |
| {//add the fragment |
| rtl::OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DECODE_WITH_CHARSET ); |
| aLine.append("/GoToR"); |
| aLine.append("/F"); |
| bFileSpec = true; |
| appendLiteralStringEncrypt( bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark, |
| INetURLObject::WAS_ENCODED, |
| INetURLObject::DECODE_WITH_CHARSET ) : |
| aURLNoMark, rLink.m_nObject, aLine, osl_getThreadTextEncoding() ); |
| if( aFragment.getLength() > 0 ) |
| { |
| aLine.append("/D/"); |
| appendDestinationName( aFragment , aLine ); |
| } |
| } |
| else |
| { |
| // change the fragment to accomodate the bookmark (only if the file extension is PDF and |
| // the requested action is of the correct type) |
| if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination && |
| bTargetHasPDFExtension && aFragment.getLength() > 0 ) |
| { |
| OStringBuffer aLineLoc( 1024 ); |
| appendDestinationName( aFragment , aLineLoc ); |
| //substitute the fragment |
| aTargetURL.SetMark( aLineLoc.getStr() ); |
| } |
| rtl::OUString aURL = aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DECODE_WITH_CHARSET : INetURLObject::NO_DECODE ); |
| // check if we have a URL available, if the string is empty, set it as the original one |
| // if( aURL.getLength() == 0 ) |
| // appendLiteralStringEncrypt( rLink.m_aURL , rLink.m_nObject, aLine ); |
| // else |
| appendLiteralStringEncrypt( bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL, |
| INetURLObject::WAS_ENCODED, |
| bFileSpec ? INetURLObject::DECODE_WITH_CHARSET : INetURLObject::NO_DECODE |
| ) : |
| aURL , rLink.m_nObject, aLine, osl_getThreadTextEncoding() ); |
| } |
| //<--- i56629 |
| } |
| aLine.append( ">>\n" ); |
| } |
| if( rLink.m_nStructParent > 0 ) |
| { |
| aLine.append( "/StructParent " ); |
| aLine.append( rLink.m_nStructParent ); |
| } |
| aLine.append( ">>\nendobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| } |
| |
| return true; |
| } |
| |
| bool PDFWriterImpl::emitNoteAnnotations() |
| { |
| // emit note annotations |
| int nAnnots = m_aNotes.size(); |
| for( int i = 0; i < nAnnots; i++ ) |
| { |
| const PDFNoteEntry& rNote = m_aNotes[i]; |
| if( ! updateObject( rNote.m_nObject ) ) |
| return false; |
| |
| OStringBuffer aLine( 1024 ); |
| aLine.append( rNote.m_nObject ); |
| aLine.append( " 0 obj\n" ); |
| //i59651 key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should' |
| // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3 |
| aLine.append( "<</Type/Annot" ); |
| if( m_bIsPDF_A1 ) |
| aLine.append( "/F 4" ); |
| aLine.append( "/Subtype/Text/Rect[" ); |
| |
| appendFixedInt( rNote.m_aRect.Left(), aLine ); |
| aLine.append( ' ' ); |
| appendFixedInt( rNote.m_aRect.Top(), aLine ); |
| aLine.append( ' ' ); |
| appendFixedInt( rNote.m_aRect.Right(), aLine ); |
| aLine.append( ' ' ); |
| appendFixedInt( rNote.m_aRect.Bottom(), aLine ); |
| aLine.append( "]" ); |
| |
| // contents of the note (type text string) |
| aLine.append( "/Contents\n" ); |
| appendUnicodeTextStringEncrypt( rNote.m_aContents.Contents, rNote.m_nObject, aLine ); |
| aLine.append( "\n" ); |
| |
| // optional title |
| if( rNote.m_aContents.Title.Len() ) |
| { |
| aLine.append( "/T" ); |
| appendUnicodeTextStringEncrypt( rNote.m_aContents.Title, rNote.m_nObject, aLine ); |
| aLine.append( "\n" ); |
| } |
| |
| aLine.append( ">>\nendobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| } |
| return true; |
| } |
| |
| Font PDFWriterImpl::replaceFont( const Font& rControlFont, const Font& rAppSetFont ) |
| { |
| bool bAdjustSize = false; |
| |
| Font aFont( rControlFont ); |
| if( ! aFont.GetName().Len() ) |
| { |
| aFont = rAppSetFont; |
| if( rControlFont.GetHeight() ) |
| aFont.SetSize( Size( 0, rControlFont.GetHeight() ) ); |
| else |
| bAdjustSize = true; |
| if( rControlFont.GetItalic() != ITALIC_DONTKNOW ) |
| aFont.SetItalic( rControlFont.GetItalic() ); |
| if( rControlFont.GetWeight() != WEIGHT_DONTKNOW ) |
| aFont.SetWeight( rControlFont.GetWeight() ); |
| } |
| else if( ! aFont.GetHeight() ) |
| { |
| aFont.SetSize( rAppSetFont.GetSize() ); |
| bAdjustSize = true; |
| } |
| if( bAdjustSize ) |
| { |
| Size aFontSize = aFont.GetSize(); |
| OutputDevice* pDefDev = Application::GetDefaultDevice(); |
| aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() ); |
| aFont.SetSize( aFontSize ); |
| } |
| return aFont; |
| } |
| |
| sal_Int32 PDFWriterImpl::getBestBuiltinFont( const Font& rFont ) |
| { |
| sal_Int32 nBest = 4; // default to Helvetica |
| OUString aFontName( rFont.GetName() ); |
| aFontName = aFontName.toAsciiLowerCase(); |
| |
| if( aFontName.indexOf( OUString( RTL_CONSTASCII_USTRINGPARAM( "times" ) ) ) != -1 ) |
| nBest = 8; |
| else if( aFontName.indexOf( OUString( RTL_CONSTASCII_USTRINGPARAM( "courier" ) ) ) != -1 ) |
| nBest = 0; |
| else if( aFontName.indexOf( OUString( RTL_CONSTASCII_USTRINGPARAM( "dingbats" ) ) ) != -1 ) |
| nBest = 13; |
| else if( aFontName.indexOf( OUString( RTL_CONSTASCII_USTRINGPARAM( "symbol" ) ) ) != -1 ) |
| nBest = 12; |
| if( nBest < 12 ) |
| { |
| if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL ) |
| nBest += 1; |
| if( rFont.GetWeight() > WEIGHT_MEDIUM ) |
| nBest += 2; |
| } |
| |
| if( m_aBuiltinFontToObjectMap.find( nBest ) == m_aBuiltinFontToObjectMap.end() ) |
| m_aBuiltinFontToObjectMap[ nBest ] = createObject(); |
| |
| return nBest; |
| } |
| |
| static inline const Color& replaceColor( const Color& rCol1, const Color& rCol2 ) |
| { |
| return (rCol1 == Color( COL_TRANSPARENT )) ? rCol2 : rCol1; |
| } |
| |
| void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget ) |
| { |
| const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); |
| |
| // save graphics state |
| push( sal::static_int_cast<sal_uInt16>(~0U) ); |
| |
| // transform relative to control's coordinates since an |
| // appearance stream is a form XObject |
| // this relies on the m_aRect member of rButton NOT already being transformed |
| // to default user space |
| if( rWidget.Background || rWidget.Border ) |
| { |
| setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : Color( COL_TRANSPARENT ) ); |
| setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : Color( COL_TRANSPARENT ) ); |
| drawRectangle( rWidget.Location ); |
| } |
| // prepare font to use |
| Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() ); |
| setFont( aFont ); |
| setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) ); |
| |
| drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle ); |
| |
| // create DA string while local mapmode is still in place |
| // (that is before endRedirect()) |
| OStringBuffer aDA( 256 ); |
| appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA ); |
| Font aDummyFont( String( RTL_CONSTASCII_USTRINGPARAM( "Helvetica" ) ), aFont.GetSize() ); |
| sal_Int32 nDummyBuiltin = getBestBuiltinFont( aDummyFont ); |
| aDA.append( ' ' ); |
| aDA.append( m_aBuiltinFonts[nDummyBuiltin].getNameObject() ); |
| aDA.append( ' ' ); |
| m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetHeight() ), aDA ); |
| aDA.append( " Tf" ); |
| rButton.m_aDAString = aDA.makeStringAndClear(); |
| |
| pop(); |
| |
| rButton.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream(); |
| |
| /* seems like a bad hack but at least works in both AR5 and 6: |
| we draw the button ourselves and tell AR |
| the button would be totally transparent with no text |
| |
| One would expect that simply setting a normal appearance |
| should suffice, but no, as soon as the user actually presses |
| the button and an action is tied to it (gasp! a button that |
| does something) the appearance gets replaced by some crap that AR |
| creates on the fly even if no DA or MK is given. On AR6 at least |
| the DA and MK work as expected, but on AR5 this creates a region |
| filled with the background color but nor text. Urgh. |
| */ |
| rButton.m_aMKDict = "/BC [] /BG [] /CA"; |
| rButton.m_aMKDictCAString = ""; |
| } |
| |
| Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern, |
| const PDFWriter::AnyWidget& rWidget, |
| const StyleSettings& rSettings ) |
| { |
| Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() ); |
| |
| if( rWidget.Background || rWidget.Border ) |
| { |
| if( rWidget.Border && rWidget.BorderColor == Color( COL_TRANSPARENT ) ) |
| { |
| sal_Int32 nDelta = getReferenceDevice()->ImplGetDPIX() / 500; |
| if( nDelta < 1 ) |
| nDelta = 1; |
| setLineColor( Color( COL_TRANSPARENT ) ); |
| Rectangle aRect = rIntern.m_aRect; |
| setFillColor( rSettings.GetLightBorderColor() ); |
| drawRectangle( aRect ); |
| aRect.Left() += nDelta; aRect.Top() += nDelta; |
| aRect.Right() -= nDelta; aRect.Bottom() -= nDelta; |
| setFillColor( rSettings.GetFieldColor() ); |
| drawRectangle( aRect ); |
| setFillColor( rSettings.GetLightColor() ); |
| drawRectangle( Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) ); |
| drawRectangle( Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) ); |
| setFillColor( rSettings.GetDarkShadowColor() ); |
| drawRectangle( Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) ); |
| drawRectangle( Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) ); |
| } |
| else |
| { |
| setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : Color( COL_TRANSPARENT ) ); |
| setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : Color( COL_TRANSPARENT ) ); |
| drawRectangle( rIntern.m_aRect ); |
| } |
| |
| if( rWidget.Border ) |
| { |
| // adjust edit area accounting for border |
| sal_Int32 nDelta = aFont.GetHeight()/4; |
| if( nDelta < 1 ) |
| nDelta = 1; |
| rIntern.m_aRect.Left() += nDelta; |
| rIntern.m_aRect.Top() += nDelta; |
| rIntern.m_aRect.Right() -= nDelta; |
| rIntern.m_aRect.Bottom()-= nDelta; |
| } |
| } |
| return aFont; |
| } |
| |
| void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget ) |
| { |
| const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); |
| SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 ); |
| |
| push( sal::static_int_cast<sal_uInt16>(~0U) ); |
| |
| // prepare font to use, draw field border |
| Font aFont = drawFieldBorder( rEdit, rWidget, rSettings ); |
| sal_Int32 nBest = m_aContext.FieldsUseSystemFonts ? getSystemFont( aFont ): getBestBuiltinFont( aFont ); |
| |
| // prepare DA string |
| OStringBuffer aDA( 32 ); |
| appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA ); |
| aDA.append( ' ' ); |
| if( m_aContext.FieldsUseSystemFonts ) |
| { |
| aDA.append( "/F" ); |
| aDA.append( nBest ); |
| |
| OStringBuffer aDR( 32 ); |
| aDR.append( "/Font " ); |
| aDR.append( getFontDictObject() ); |
| aDR.append( " 0 R" ); |
| rEdit.m_aDRDict = aDR.makeStringAndClear(); |
| } |
| else |
| aDA.append( m_aBuiltinFonts[nBest].getNameObject() ); |
| aDA.append( ' ' ); |
| m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetHeight() ), aDA ); |
| aDA.append( " Tf" ); |
| |
| /* create an empty appearance stream, let the viewer create |
| the appearance at runtime. This is because AR5 seems to |
| paint the widget appearance always, and a dynamically created |
| appearance on top of it. AR6 is well behaved in that regard, so |
| that behaviour seems to be a bug. Anyway this empty appearance |
| relies on /NeedAppearances in the AcroForm dictionary set to "true" |
| */ |
| beginRedirect( pEditStream, rEdit.m_aRect ); |
| OStringBuffer aAppearance( 32 ); |
| aAppearance.append( "/Tx BMC\nEMC\n" ); |
| writeBuffer( aAppearance.getStr(), aAppearance.getLength() ); |
| |
| endRedirect(); |
| pop(); |
| |
| rEdit.m_aAppearances[ "N" ][ "Standard" ] = pEditStream; |
| |
| rEdit.m_aDAString = aDA.makeStringAndClear(); |
| } |
| |
| void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget ) |
| { |
| const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); |
| SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 ); |
| |
| push( sal::static_int_cast<sal_uInt16>(~0U) ); |
| |
| // prepare font to use, draw field border |
| Font aFont = drawFieldBorder( rBox, rWidget, rSettings ); |
| sal_Int32 nBest = m_aContext.FieldsUseSystemFonts ? getSystemFont( aFont ): getBestBuiltinFont( aFont ); |
| |
| beginRedirect( pListBoxStream, rBox.m_aRect ); |
| OStringBuffer aAppearance( 64 ); |
| |
| #if 0 |
| if( ! rWidget.DropDown ) |
| { |
| // prepare linewidth for DA string hack, see below |
| Size aFontSize = lcl_convert( m_aGraphicsStack.front().m_aMapMode, |
| m_aMapMode, |
| getReferenceDevice(), |
| Size( 0, aFont.GetHeight() ) ); |
| sal_Int32 nLW = aFontSize.Height() / 40; |
| appendFixedInt( nLW > 0 ? nLW : 1, aAppearance ); |
| aAppearance.append( " w\n" ); |
| writeBuffer( aAppearance.getStr(), aAppearance.getLength() ); |
| aAppearance.setLength( 0 ); |
| } |
| #endif |
| |
| setLineColor( Color( COL_TRANSPARENT ) ); |
| setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) ); |
| drawRectangle( rBox.m_aRect ); |
| |
| // empty appearance, see createDefaultEditAppearance for reference |
| aAppearance.append( "/Tx BMC\nEMC\n" ); |
| writeBuffer( aAppearance.getStr(), aAppearance.getLength() ); |
| |
| endRedirect(); |
| pop(); |
| |
| rBox.m_aAppearances[ "N" ][ "Standard" ] = pListBoxStream; |
| |
| // prepare DA string |
| OStringBuffer aDA( 256 ); |
| #if 0 |
| if( !rWidget.DropDown ) |
| { |
| /* another of AR5's peculiarities: the selected item of a choice |
| field is highlighted using the non stroking color - same as the |
| text color. so workaround that by using text rendering mode 2 |
| (fill, then stroke) and set the stroking color |
| */ |
| appendStrokingColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ), aDA ); |
| aDA.append( " 2 Tr " ); |
| } |
| #endif |
| // prepare DA string |
| appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA ); |
| aDA.append( ' ' ); |
| if( m_aContext.FieldsUseSystemFonts ) |
| { |
| aDA.append( "/F" ); |
| aDA.append( nBest ); |
| |
| OStringBuffer aDR( 32 ); |
| aDR.append( "/Font " ); |
| aDR.append( getFontDictObject() ); |
| aDR.append( " 0 R" ); |
| rBox.m_aDRDict = aDR.makeStringAndClear(); |
| } |
| else |
| aDA.append( m_aBuiltinFonts[nBest].getNameObject() ); |
| aDA.append( ' ' ); |
| m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetHeight() ), aDA ); |
| aDA.append( " Tf" ); |
| rBox.m_aDAString = aDA.makeStringAndClear(); |
| } |
| |
| void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget ) |
| { |
| const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); |
| |
| // save graphics state |
| push( sal::static_int_cast<sal_uInt16>(~0U) ); |
| |
| if( rWidget.Background || rWidget.Border ) |
| { |
| setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : Color( COL_TRANSPARENT ) ); |
| setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : Color( COL_TRANSPARENT ) ); |
| drawRectangle( rBox.m_aRect ); |
| } |
| |
| Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() ); |
| setFont( aFont ); |
| Size aFontSize = aFont.GetSize(); |
| if( aFontSize.Height() > rBox.m_aRect.GetHeight() ) |
| aFontSize.Height() = rBox.m_aRect.GetHeight(); |
| sal_Int32 nDelta = aFontSize.Height()/10; |
| if( nDelta < 1 ) |
| nDelta = 1; |
| |
| Rectangle aCheckRect, aTextRect; |
| if( rWidget.ButtonIsLeft ) |
| { |
| aCheckRect.Left() = rBox.m_aRect.Left() + nDelta; |
| aCheckRect.Top() = rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2; |
| aCheckRect.Right() = aCheckRect.Left() + aFontSize.Height(); |
| aCheckRect.Bottom() = aCheckRect.Top() + aFontSize.Height(); |
| |
| // #i74206# handle small controls without text area |
| while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta ) |
| { |
| aCheckRect.Right() -= nDelta; |
| aCheckRect.Top() += nDelta/2; |
| aCheckRect.Bottom() -= nDelta - (nDelta/2); |
| } |
| |
| aTextRect.Left() = rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta; |
| aTextRect.Top() = rBox.m_aRect.Top(); |
| aTextRect.Right() = aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta; |
| aTextRect.Bottom() = rBox.m_aRect.Bottom(); |
| } |
| else |
| { |
| aCheckRect.Left() = rBox.m_aRect.Right() - nDelta - aFontSize.Height(); |
| aCheckRect.Top() = rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2; |
| aCheckRect.Right() = aCheckRect.Left() + aFontSize.Height(); |
| aCheckRect.Bottom() = aCheckRect.Top() + aFontSize.Height(); |
| |
| // #i74206# handle small controls without text area |
| while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta ) |
| { |
| aCheckRect.Left() += nDelta; |
| aCheckRect.Top() += nDelta/2; |
| aCheckRect.Bottom() -= nDelta - (nDelta/2); |
| } |
| |
| aTextRect.Left() = rBox.m_aRect.Left(); |
| aTextRect.Top() = rBox.m_aRect.Top(); |
| aTextRect.Right() = aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta; |
| aTextRect.Bottom() = rBox.m_aRect.Bottom(); |
| } |
| setLineColor( Color( COL_BLACK ) ); |
| setFillColor( Color( COL_TRANSPARENT ) ); |
| OStringBuffer aLW( 32 ); |
| aLW.append( "q " ); |
| m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW ); |
| aLW.append( " w " ); |
| writeBuffer( aLW.getStr(), aLW.getLength() ); |
| drawRectangle( aCheckRect ); |
| writeBuffer( " Q\n", 3 ); |
| setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) ); |
| drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle ); |
| |
| pop(); |
| |
| OStringBuffer aDA( 256 ); |
| appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); |
| sal_Int32 nBest = getBestBuiltinFont( Font( String( RTL_CONSTASCII_USTRINGPARAM( "ZapfDingbats" ) ), aFont.GetSize() ) ); |
| aDA.append( ' ' ); |
| aDA.append( m_aBuiltinFonts[nBest].getNameObject() ); |
| aDA.append( " 0 Tf" ); |
| rBox.m_aDAString = aDA.makeStringAndClear(); |
| rBox.m_aMKDict = "/CA"; |
| rBox.m_aMKDictCAString = "8"; |
| rBox.m_aRect = aCheckRect; |
| |
| // create appearance streams |
| sal_Char cMark = '8'; |
| sal_Int32 nCharXOffset = 1000-m_aBuiltinFonts[13].m_aWidths[sal_Int32(cMark)]; |
| nCharXOffset *= aCheckRect.GetHeight(); |
| nCharXOffset /= 2000; |
| sal_Int32 nCharYOffset = 1000- |
| (m_aBuiltinFonts[13].m_nAscent+m_aBuiltinFonts[13].m_nDescent); // descent is negative |
| nCharYOffset *= aCheckRect.GetHeight(); |
| nCharYOffset /= 2000; |
| |
| SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 ); |
| beginRedirect( pCheckStream, aCheckRect ); |
| aDA.append( "/Tx BMC\nq BT\n" ); |
| appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); |
| aDA.append( ' ' ); |
| aDA.append( m_aBuiltinFonts[nBest].getNameObject() ); |
| aDA.append( ' ' ); |
| m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA ); |
| aDA.append( " Tf\n" ); |
| m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA ); |
| aDA.append( " " ); |
| m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA ); |
| aDA.append( " Td (" ); |
| aDA.append( cMark ); |
| aDA.append( ") Tj\nET\nQ\nEMC\n" ); |
| writeBuffer( aDA.getStr(), aDA.getLength() ); |
| endRedirect(); |
| rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream; |
| |
| SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 ); |
| beginRedirect( pUncheckStream, aCheckRect ); |
| writeBuffer( "/Tx BMC\nEMC\n", 12 ); |
| endRedirect(); |
| rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream; |
| } |
| |
| void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget ) |
| { |
| const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); |
| |
| // save graphics state |
| push( sal::static_int_cast<sal_uInt16>(~0U) ); |
| |
| if( rWidget.Background || rWidget.Border ) |
| { |
| setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : Color( COL_TRANSPARENT ) ); |
| setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : Color( COL_TRANSPARENT ) ); |
| drawRectangle( rBox.m_aRect ); |
| } |
| |
| Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() ); |
| setFont( aFont ); |
| Size aFontSize = aFont.GetSize(); |
| if( aFontSize.Height() > rBox.m_aRect.GetHeight() ) |
| aFontSize.Height() = rBox.m_aRect.GetHeight(); |
| sal_Int32 nDelta = aFontSize.Height()/10; |
| if( nDelta < 1 ) |
| nDelta = 1; |
| |
| Rectangle aCheckRect, aTextRect; |
| if( rWidget.ButtonIsLeft ) |
| { |
| aCheckRect.Left() = rBox.m_aRect.Left() + nDelta; |
| aCheckRect.Top() = rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2; |
| aCheckRect.Right() = aCheckRect.Left() + aFontSize.Height(); |
| aCheckRect.Bottom() = aCheckRect.Top() + aFontSize.Height(); |
| |
| // #i74206# handle small controls without text area |
| while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta ) |
| { |
| aCheckRect.Right() -= nDelta; |
| aCheckRect.Top() += nDelta/2; |
| aCheckRect.Bottom() -= nDelta - (nDelta/2); |
| } |
| |
| aTextRect.Left() = rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta; |
| aTextRect.Top() = rBox.m_aRect.Top(); |
| aTextRect.Right() = aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta; |
| aTextRect.Bottom() = rBox.m_aRect.Bottom(); |
| } |
| else |
| { |
| aCheckRect.Left() = rBox.m_aRect.Right() - nDelta - aFontSize.Height(); |
| aCheckRect.Top() = rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2; |
| aCheckRect.Right() = aCheckRect.Left() + aFontSize.Height(); |
| aCheckRect.Bottom() = aCheckRect.Top() + aFontSize.Height(); |
| |
| // #i74206# handle small controls without text area |
| while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta ) |
| { |
| aCheckRect.Left() += nDelta; |
| aCheckRect.Top() += nDelta/2; |
| aCheckRect.Bottom() -= nDelta - (nDelta/2); |
| } |
| |
| aTextRect.Left() = rBox.m_aRect.Left(); |
| aTextRect.Top() = rBox.m_aRect.Top(); |
| aTextRect.Right() = aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta; |
| aTextRect.Bottom() = rBox.m_aRect.Bottom(); |
| } |
| setLineColor( Color( COL_BLACK ) ); |
| setFillColor( Color( COL_TRANSPARENT ) ); |
| OStringBuffer aLW( 32 ); |
| aLW.append( "q " ); |
| m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW ); |
| aLW.append( " w " ); |
| writeBuffer( aLW.getStr(), aLW.getLength() ); |
| drawEllipse( aCheckRect ); |
| writeBuffer( " Q\n", 3 ); |
| setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) ); |
| drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle ); |
| |
| pop(); |
| |
| OStringBuffer aDA( 256 ); |
| appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); |
| sal_Int32 nBest = getBestBuiltinFont( Font( String( RTL_CONSTASCII_USTRINGPARAM( "ZapfDingbats" ) ), aFont.GetSize() ) ); |
| aDA.append( ' ' ); |
| aDA.append( m_aBuiltinFonts[nBest].getNameObject() ); |
| aDA.append( " 0 Tf" ); |
| rBox.m_aDAString = aDA.makeStringAndClear(); |
| //to encrypt this (el) |
| rBox.m_aMKDict = "/CA"; |
| //after this assignement, to m_aMKDic cannot be added anything |
| rBox.m_aMKDictCAString = "l"; |
| |
| rBox.m_aRect = aCheckRect; |
| |
| // create appearance streams |
| push( sal::static_int_cast<sal_uInt16>(~0U) ); |
| SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 ); |
| |
| beginRedirect( pCheckStream, aCheckRect ); |
| aDA.append( "/Tx BMC\nq BT\n" ); |
| appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); |
| aDA.append( ' ' ); |
| aDA.append( m_aBuiltinFonts[nBest].getNameObject() ); |
| aDA.append( ' ' ); |
| m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA ); |
| aDA.append( " Tf\n0 0 Td\nET\nQ\n" ); |
| writeBuffer( aDA.getStr(), aDA.getLength() ); |
| setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) ); |
| setLineColor( Color( COL_TRANSPARENT ) ); |
| aCheckRect.Left() += 3*nDelta; |
| aCheckRect.Top() += 3*nDelta; |
| aCheckRect.Bottom() -= 3*nDelta; |
| aCheckRect.Right() -= 3*nDelta; |
| drawEllipse( aCheckRect ); |
| writeBuffer( "\nEMC\n", 5 ); |
| endRedirect(); |
| |
| pop(); |
| rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream; |
| |
| SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 ); |
| beginRedirect( pUncheckStream, aCheckRect ); |
| writeBuffer( "/Tx BMC\nEMC\n", 12 ); |
| endRedirect(); |
| rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream; |
| } |
| |
| bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict ) |
| { |
| |
| // TODO: check and insert default streams |
| rtl::OString aStandardAppearance; |
| switch( rWidget.m_eType ) |
| { |
| case PDFWriter::CheckBox: |
| aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US ); |
| break; |
| default: |
| break; |
| } |
| |
| if( rWidget.m_aAppearances.size() ) |
| { |
| rAnnotDict.append( "/AP<<\n" ); |
| for( PDFAppearanceMap::iterator dict_it = rWidget.m_aAppearances.begin(); dict_it != rWidget.m_aAppearances.end(); ++dict_it ) |
| { |
| rAnnotDict.append( "/" ); |
| rAnnotDict.append( dict_it->first ); |
| bool bUseSubDict = (dict_it->second.size() > 1); |
| rAnnotDict.append( bUseSubDict ? "<<" : " " ); |
| |
| for( PDFAppearanceStreams::const_iterator stream_it = dict_it->second.begin(); |
| stream_it != dict_it->second.end(); ++stream_it ) |
| { |
| SvMemoryStream* pApppearanceStream = stream_it->second; |
| dict_it->second[ stream_it->first ] = NULL; |
| |
| bool bDeflate = compressStream( pApppearanceStream ); |
| |
| pApppearanceStream->Seek( STREAM_SEEK_TO_END ); |
| sal_Int64 nStreamLen = pApppearanceStream->Tell(); |
| pApppearanceStream->Seek( STREAM_SEEK_TO_BEGIN ); |
| sal_Int32 nObject = createObject(); |
| CHECK_RETURN( updateObject( nObject ) ); |
| #if OSL_DEBUG_LEVEL > 1 |
| emitComment( "PDFWriterImpl::emitAppearances" ); |
| #endif |
| OStringBuffer aLine; |
| aLine.append( nObject ); |
| |
| aLine.append( " 0 obj\n" |
| "<</Type/XObject\n" |
| "/Subtype/Form\n" |
| "/BBox[0 0 " ); |
| appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine ); |
| aLine.append( " " ); |
| appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine ); |
| aLine.append( "]\n" |
| "/Resources " ); |
| aLine.append( getResourceDictObj() ); |
| aLine.append( " 0 R\n" |
| "/Length " ); |
| aLine.append( nStreamLen ); |
| aLine.append( "\n" ); |
| if( bDeflate ) |
| aLine.append( "/Filter/FlateDecode\n" ); |
| aLine.append( ">>\nstream\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| checkAndEnableStreamEncryption( nObject ); |
| CHECK_RETURN( writeBuffer( pApppearanceStream->GetData(), nStreamLen ) ); |
| disableStreamEncryption(); |
| CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n", 19 ) ); |
| |
| if( bUseSubDict ) |
| { |
| rAnnotDict.append( " /" ); |
| rAnnotDict.append( stream_it->first ); |
| rAnnotDict.append( " " ); |
| } |
| rAnnotDict.append( nObject ); |
| rAnnotDict.append( " 0 R" ); |
| |
| delete pApppearanceStream; |
| } |
| |
| rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" ); |
| } |
| rAnnotDict.append( ">>\n" ); |
| if( aStandardAppearance.getLength() ) |
| { |
| rAnnotDict.append( "/AS /" ); |
| rAnnotDict.append( aStandardAppearance ); |
| rAnnotDict.append( "\n" ); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool PDFWriterImpl::emitWidgetAnnotations() |
| { |
| ensureUniqueRadioOnValues(); |
| |
| int nAnnots = m_aWidgets.size(); |
| for( int a = 0; a < nAnnots; a++ ) |
| { |
| PDFWidget& rWidget = m_aWidgets[a]; |
| |
| OStringBuffer aLine( 1024 ); |
| OStringBuffer aValue( 256 ); |
| aLine.append( rWidget.m_nObject ); |
| aLine.append( " 0 obj\n" |
| "<<" ); |
| if( rWidget.m_eType != PDFWriter::Hierarchy ) |
| { |
| // emit widget annotation only for terminal fields |
| if( rWidget.m_aKids.empty() ) |
| { |
| aLine.append( "/Type/Annot/Subtype/Widget/F 4\n" |
| "/Rect[" ); |
| appendFixedInt( rWidget.m_aRect.Left()-1, aLine ); |
| aLine.append( ' ' ); |
| appendFixedInt( rWidget.m_aRect.Top()+1, aLine ); |
| aLine.append( ' ' ); |
| appendFixedInt( rWidget.m_aRect.Right()+1, aLine ); |
| aLine.append( ' ' ); |
| appendFixedInt( rWidget.m_aRect.Bottom()-1, aLine ); |
| aLine.append( "]\n" ); |
| } |
| aLine.append( "/FT/" ); |
| switch( rWidget.m_eType ) |
| { |
| case PDFWriter::RadioButton: |
| case PDFWriter::CheckBox: |
| // for radio buttons only the RadioButton field, not the |
| // CheckBox children should have a value, else acrobat reader |
| // does not always check the right button |
| // of course real check boxes (not belonging to a readio group) |
| // need their values, too |
| if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 ) |
| { |
| aValue.append( "/" ); |
| // check for radio group with all buttons unpressed |
| if( rWidget.m_aValue.getLength() == 0 ) |
| aValue.append( "Off" ); |
| else |
| appendName( rWidget.m_aValue, aValue ); |
| } |
| case PDFWriter::PushButton: |
| aLine.append( "Btn" ); |
| break; |
| case PDFWriter::ListBox: |
| if( rWidget.m_nFlags & 0x200000 ) // multiselect |
| { |
| aValue.append( "[" ); |
| for( unsigned int i = 0; i < rWidget.m_aSelectedEntries.size(); i++ ) |
| { |
| sal_Int32 nEntry = rWidget.m_aSelectedEntries[i]; |
| if( nEntry >= 0 && nEntry < sal_Int32(rWidget.m_aListEntries.size()) ) |
| appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject, aValue ); |
| } |
| aValue.append( "]" ); |
| } |
| else if( rWidget.m_aSelectedEntries.size() > 0 && |
| rWidget.m_aSelectedEntries[0] >= 0 && |
| rWidget.m_aSelectedEntries[0] < sal_Int32(rWidget.m_aListEntries.size()) ) |
| { |
| appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject, aValue ); |
| } |
| else |
| appendUnicodeTextStringEncrypt( rtl::OUString(), rWidget.m_nObject, aValue ); |
| aLine.append( "Ch" ); |
| break; |
| case PDFWriter::ComboBox: |
| appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue ); |
| aLine.append( "Ch" ); |
| break; |
| case PDFWriter::Edit: |
| aLine.append( "Tx" ); |
| appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue ); |
| break; |
| case PDFWriter::Hierarchy: // make the compiler happy |
| break; |
| } |
| aLine.append( "\n" ); |
| aLine.append( "/P " ); |
| aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject ); |
| aLine.append( " 0 R\n" ); |
| } |
| if( rWidget.m_nParent ) |
| { |
| aLine.append( "/Parent " ); |
| aLine.append( rWidget.m_nParent ); |
| aLine.append( " 0 R\n" ); |
| } |
| if( rWidget.m_aKids.size() ) |
| { |
| aLine.append( "/Kids[" ); |
| for( unsigned int i = 0; i < rWidget.m_aKids.size(); i++ ) |
| { |
| aLine.append( rWidget.m_aKids[i] ); |
| aLine.append( " 0 R" ); |
| aLine.append( ( (i&15) == 15 ) ? "\n" : " " ); |
| } |
| aLine.append( "]\n" ); |
| } |
| if( rWidget.m_aName.getLength() ) |
| { |
| aLine.append( "/T" ); |
| appendLiteralStringEncrypt( rWidget.m_aName, rWidget.m_nObject, aLine ); |
| aLine.append( "\n" ); |
| } |
| if( m_aContext.Version > PDFWriter::PDF_1_2 && rWidget.m_aDescription.getLength() ) |
| { |
| // the alternate field name should be unicode able since it is |
| // supposed to be used in UI |
| aLine.append( "/TU" ); |
| appendUnicodeTextStringEncrypt( rWidget.m_aDescription, rWidget.m_nObject, aLine ); |
| aLine.append( "\n" ); |
| } |
| |
| if( rWidget.m_nFlags ) |
| { |
| aLine.append( "/Ff " ); |
| aLine.append( rWidget.m_nFlags ); |
| aLine.append( "\n" ); |
| } |
| if( aValue.getLength() ) |
| { |
| OString aVal = aValue.makeStringAndClear(); |
| aLine.append( "/V " ); |
| aLine.append( aVal ); |
| aLine.append( "\n" |
| "/DV " ); |
| aLine.append( aVal ); |
| aLine.append( "\n" ); |
| } |
| if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox ) |
| { |
| sal_Int32 nTI = -1; |
| aLine.append( "/Opt[\n" ); |
| sal_Int32 i = 0; |
| for( std::vector< OUString >::const_iterator it = rWidget.m_aListEntries.begin(); it != rWidget.m_aListEntries.end(); ++it, ++i ) |
| { |
| appendUnicodeTextStringEncrypt( *it, rWidget.m_nObject, aLine ); |
| aLine.append( "\n" ); |
| if( *it == rWidget.m_aValue ) |
| nTI = i; |
| } |
| aLine.append( "]\n" ); |
| if( nTI > 0 ) |
| { |
| aLine.append( "/TI " ); |
| aLine.append( nTI ); |
| aLine.append( "\n" ); |
| if( rWidget.m_nFlags & 0x200000 ) // Multiselect |
| { |
| aLine.append( "/I [" ); |
| aLine.append( nTI ); |
| aLine.append( "]\n" ); |
| } |
| } |
| } |
| if( rWidget.m_eType == PDFWriter::Edit && rWidget.m_nMaxLen > 0 ) |
| { |
| aLine.append( "/MaxLen " ); |
| aLine.append( rWidget.m_nMaxLen ); |
| aLine.append( "\n" ); |
| } |
| if( rWidget.m_eType == PDFWriter::PushButton ) |
| { |
| if(!m_bIsPDF_A1) |
| { |
| OStringBuffer aDest; |
| if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) ) |
| { |
| aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " ); |
| aLine.append( aDest.makeStringAndClear() ); |
| aLine.append( ">>>>\n" ); |
| } |
| else if( rWidget.m_aListEntries.empty() ) |
| { |
| // create a reset form action |
| aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" ); |
| } |
| else if( rWidget.m_bSubmit ) |
| { |
| // create a submit form action |
| aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" ); |
| appendLiteralStringEncrypt( rWidget.m_aListEntries.front(), rWidget.m_nObject, aLine, osl_getThreadTextEncoding() ); |
| aLine.append( "/Flags " ); |
| |
| sal_Int32 nFlags = 0; |
| switch( m_aContext.SubmitFormat ) |
| { |
| case PDFWriter::HTML: |
| nFlags |= 4; |
| break; |
| case PDFWriter::XML: |
| if( m_aContext.Version > PDFWriter::PDF_1_3 ) |
| nFlags |= 32; |
| break; |
| case PDFWriter::PDF: |
| if( m_aContext.Version > PDFWriter::PDF_1_3 ) |
| nFlags |= 256; |
| break; |
| case PDFWriter::FDF: |
| default: |
| break; |
| } |
| if( rWidget.m_bSubmitGet ) |
| nFlags |= 8; |
| aLine.append( nFlags ); |
| aLine.append( ">>>>\n" ); |
| } |
| else |
| { |
| // create a URI action |
| aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" ); |
| aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) ); |
| aLine.append( ")>>>>\n" ); |
| } |
| } |
| else |
| m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA ); |
| } |
| if( rWidget.m_aDAString.getLength() ) |
| { |
| if( rWidget.m_aDRDict.getLength() ) |
| { |
| aLine.append( "/DR<<" ); |
| aLine.append( rWidget.m_aDRDict ); |
| aLine.append( ">>\n" ); |
| } |
| else |
| { |
| aLine.append( "/DR<</Font<<" ); |
| appendBuiltinFontsToDict( aLine ); |
| aLine.append( ">>>>\n" ); |
| } |
| aLine.append( "/DA" ); |
| appendLiteralStringEncrypt( rWidget.m_aDAString, rWidget.m_nObject, aLine ); |
| aLine.append( "\n" ); |
| if( rWidget.m_nTextStyle & TEXT_DRAW_CENTER ) |
| aLine.append( "/Q 1\n" ); |
| else if( rWidget.m_nTextStyle & TEXT_DRAW_RIGHT ) |
| aLine.append( "/Q 2\n" ); |
| } |
| // appearance charactristics for terminal fields |
| // which are supposed to have an appearance constructed |
| // by the viewer application |
| if( rWidget.m_aMKDict.getLength() ) |
| { |
| aLine.append( "/MK<<" ); |
| aLine.append( rWidget.m_aMKDict ); |
| //add the CA string, encrypting it |
| appendLiteralStringEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject, aLine); |
| aLine.append( ">>\n" ); |
| } |
| |
| CHECK_RETURN( emitAppearances( rWidget, aLine ) ); |
| |
| aLine.append( ">>\n" |
| "endobj\n\n" ); |
| CHECK_RETURN( updateObject( rWidget.m_nObject ) ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| } |
| return true; |
| } |
| |
| bool PDFWriterImpl::emitAnnotations() |
| { |
| if( m_aPages.size() < 1 ) |
| return false; |
| |
| CHECK_RETURN( emitLinkAnnotations() ); |
| |
| CHECK_RETURN( emitNoteAnnotations() ); |
| |
| CHECK_RETURN( emitWidgetAnnotations() ); |
| |
| return true; |
| } |
| |
| #undef CHECK_RETURN |
| #define CHECK_RETURN( x ) if( !x ) return false |
| |
| bool PDFWriterImpl::emitCatalog() |
| { |
| // build page tree |
| // currently there is only one node that contains all leaves |
| |
| // first create a page tree node id |
| sal_Int32 nTreeNode = createObject(); |
| |
| // emit global resource dictionary (page emit needs it) |
| CHECK_RETURN( emitResources() ); |
| |
| // emit all pages |
| for( std::vector<PDFPage>::iterator it = m_aPages.begin(); it != m_aPages.end(); ++it ) |
| if( ! it->emit( nTreeNode ) ) |
| return false; |
| |
| sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations(); |
| |
| sal_Int32 nOutlineDict = emitOutline(); |
| |
| //emit Output intent i59651 |
| sal_Int32 nOutputIntentObject = emitOutputIntent(); |
| |
| //emit metadata |
| sal_Int32 nMetadataObject = emitDocumentMetadata(); |
| |
| sal_Int32 nStructureDict = 0; |
| if(m_aStructure.size() > 1) |
| { |
| ///check if dummy structure containers are needed |
| addInternalStructureContainer(m_aStructure[0]); |
| nStructureDict = m_aStructure[0].m_nObject = createObject(); |
| emitStructure( m_aStructure[ 0 ] ); |
| } |
| |
| // adjust tree node file offset |
| if( ! updateObject( nTreeNode ) ) |
| return false; |
| |
| // emit tree node |
| OStringBuffer aLine( 2048 ); |
| aLine.append( nTreeNode ); |
| aLine.append( " 0 obj\n" ); |
| aLine.append( "<</Type/Pages\n" ); |
| aLine.append( "/Resources " ); |
| aLine.append( getResourceDictObj() ); |
| aLine.append( " 0 R\n" ); |
| |
| switch( m_eInheritedOrientation ) |
| { |
| case PDFWriter::Landscape: aLine.append( "/Rotate 90\n" );break; |
| case PDFWriter::Seascape: aLine.append( "/Rotate -90\n" );break; |
| |
| case PDFWriter::Inherit: // actually Inherit would be a bug, but insignificant |
| case PDFWriter::Portrait: |
| default: |
| break; |
| } |
| sal_Int32 nMediaBoxWidth = 0; |
| sal_Int32 nMediaBoxHeight = 0; |
| if( m_aPages.empty() ) // sanity check, this should not happen |
| { |
| nMediaBoxWidth = m_nInheritedPageWidth; |
| nMediaBoxHeight = m_nInheritedPageHeight; |
| } |
| else |
| { |
| for( std::vector<PDFPage>::const_iterator iter = m_aPages.begin(); iter != m_aPages.end(); ++iter ) |
| { |
| if( iter->m_nPageWidth > nMediaBoxWidth ) |
| nMediaBoxWidth = iter->m_nPageWidth; |
| if( iter->m_nPageHeight > nMediaBoxHeight ) |
| nMediaBoxHeight = iter->m_nPageHeight; |
| } |
| } |
| aLine.append( "/MediaBox[ 0 0 " ); |
| aLine.append( nMediaBoxWidth ); |
| aLine.append( ' ' ); |
| aLine.append( nMediaBoxHeight ); |
| aLine.append( " ]\n" |
| "/Kids[ " ); |
| unsigned int i = 0; |
| for( std::vector<PDFPage>::const_iterator iter = m_aPages.begin(); iter != m_aPages.end(); ++iter, i++ ) |
| { |
| aLine.append( iter->m_nPageObject ); |
| aLine.append( " 0 R" ); |
| aLine.append( ( (i&15) == 15 ) ? "\n" : " " ); |
| } |
| aLine.append( "]\n" |
| "/Count " ); |
| aLine.append( (sal_Int32)m_aPages.size() ); |
| aLine.append( ">>\n" |
| "endobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| // emit annotation objects |
| CHECK_RETURN( emitAnnotations() ); |
| |
| // emit Catalog |
| m_nCatalogObject = createObject(); |
| if( ! updateObject( m_nCatalogObject ) ) |
| return false; |
| aLine.setLength( 0 ); |
| aLine.append( m_nCatalogObject ); |
| aLine.append( " 0 obj\n" |
| "<</Type/Catalog/Pages " ); |
| aLine.append( nTreeNode ); |
| aLine.append( " 0 R\n" ); |
| //--->i56629 |
| //check if there are named destinations to emit (root must be inside the catalog) |
| if( nNamedDestinationsDictionary ) |
| { |
| aLine.append("/Dests "); |
| aLine.append( nNamedDestinationsDictionary ); |
| aLine.append( " 0 R\n" ); |
| } |
| //<---- |
| if( m_aContext.PageLayout != PDFWriter::DefaultLayout ) |
| switch( m_aContext.PageLayout ) |
| { |
| default : |
| case PDFWriter::SinglePage : |
| aLine.append( "/PageLayout/SinglePage\n" ); |
| break; |
| case PDFWriter::Continuous : |
| aLine.append( "/PageLayout/OneColumn\n" ); |
| break; |
| case PDFWriter::ContinuousFacing : |
| //the flag m_aContext.FirstPageLeft below is used to set the page on the left side |
| aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side |
| break; |
| } |
| if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode ) |
| switch( m_aContext.PDFDocumentMode ) |
| { |
| default : |
| aLine.append( "/PageMode/UseNone\n" ); |
| break; |
| case PDFWriter::UseOutlines : |
| aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open |
| break; |
| case PDFWriter::UseThumbs : |
| aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open |
| break; |
| } |
| else if( m_aContext.OpenInFullScreenMode ) |
| aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen |
| |
| OStringBuffer aInitPageRef; |
| if( m_aContext.InitialPage >= 0 && m_aContext.InitialPage < (sal_Int32)m_aPages.size() ) |
| { |
| aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject ); |
| aInitPageRef.append( " 0 R" ); |
| } |
| else |
| aInitPageRef.append( "0" ); |
| switch( m_aContext.PDFDocumentAction ) |
| { |
| case PDFWriter::ActionDefault : //do nothing, this is the Acrobat default |
| default: |
| if( aInitPageRef.getLength() > 1 ) |
| { |
| aLine.append( "/OpenAction[" ); |
| aLine.append( aInitPageRef ); |
| aLine.append( " /XYZ null null 0]\n" ); |
| } |
| break; |
| case PDFWriter::FitInWindow : |
| aLine.append( "/OpenAction[" ); |
| aLine.append( aInitPageRef ); |
| aLine.append( " /Fit]\n" ); //Open fit page |
| break; |
| case PDFWriter::FitWidth : |
| aLine.append( "/OpenAction[" ); |
| aLine.append( aInitPageRef ); |
| aLine.append( " /FitH " ); |
| aLine.append( m_nInheritedPageHeight );//Open fit width |
| aLine.append( "]\n" ); |
| break; |
| case PDFWriter::FitVisible : |
| aLine.append( "/OpenAction[" ); |
| aLine.append( aInitPageRef ); |
| aLine.append( " /FitBH " ); |
| aLine.append( m_nInheritedPageHeight );//Open fit visible |
| aLine.append( "]\n" ); |
| break; |
| case PDFWriter::ActionZoom : |
| aLine.append( "/OpenAction[" ); |
| aLine.append( aInitPageRef ); |
| aLine.append( " /XYZ null null " ); |
| if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 ) |
| aLine.append( (double)m_aContext.Zoom/100.0 ); |
| else |
| aLine.append( "0" ); |
| aLine.append( "]\n" ); |
| break; |
| } |
| // viewer preferences, if we had some, then emit |
| if( m_aContext.HideViewerToolbar || |
| ( m_aContext.Version > PDFWriter::PDF_1_3 && m_aContext.DocumentInfo.Title.Len() && m_aContext.DisplayPDFDocumentTitle ) || |
| m_aContext.HideViewerMenubar || |
| m_aContext.HideViewerWindowControls || m_aContext.FitWindow || |
| m_aContext.CenterWindow || (m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) || |
| m_aContext.OpenInFullScreenMode ) |
| { |
| aLine.append( "/ViewerPreferences<<" ); |
| if( m_aContext.HideViewerToolbar ) |
| aLine.append( "/HideToolbar true\n" ); |
| if( m_aContext.HideViewerMenubar ) |
| aLine.append( "/HideMenubar true\n" ); |
| if( m_aContext.HideViewerWindowControls ) |
| aLine.append( "/HideWindowUI true\n" ); |
| if( m_aContext.FitWindow ) |
| aLine.append( "/FitWindow true\n" ); |
| if( m_aContext.CenterWindow ) |
| aLine.append( "/CenterWindow true\n" ); |
| if( m_aContext.Version > PDFWriter::PDF_1_3 && m_aContext.DocumentInfo.Title.Len() && m_aContext.DisplayPDFDocumentTitle ) |
| aLine.append( "/DisplayDocTitle true\n" ); |
| if( m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) |
| aLine.append( "/Direction/R2L\n" ); |
| if( m_aContext.OpenInFullScreenMode ) |
| switch( m_aContext.PDFDocumentMode ) |
| { |
| default : |
| case PDFWriter::ModeDefault : |
| aLine.append( "/NonFullScreenPageMode/UseNone\n" ); |
| break; |
| case PDFWriter::UseOutlines : |
| aLine.append( "/NonFullScreenPageMode/UseOutlines\n" ); |
| break; |
| case PDFWriter::UseThumbs : |
| aLine.append( "/NonFullScreenPageMode/UseThumbs\n" ); |
| break; |
| } |
| aLine.append( ">>\n" ); |
| } |
| |
| if( nOutlineDict ) |
| { |
| aLine.append( "/Outlines " ); |
| aLine.append( nOutlineDict ); |
| aLine.append( " 0 R\n" ); |
| } |
| if( nStructureDict ) |
| { |
| aLine.append( "/StructTreeRoot " ); |
| aLine.append( nStructureDict ); |
| aLine.append( " 0 R\n" ); |
| } |
| if( m_aContext.DocumentLocale.Language.getLength() > 0 ) |
| { |
| OUStringBuffer aLocBuf( 16 ); |
| aLocBuf.append( m_aContext.DocumentLocale.Language.toAsciiLowerCase() ); |
| if( m_aContext.DocumentLocale.Country.getLength() > 0 ) |
| { |
| aLocBuf.append( sal_Unicode('-') ); |
| aLocBuf.append( m_aContext.DocumentLocale.Country ); |
| } |
| aLine.append( "/Lang" ); |
| appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), m_nCatalogObject, aLine ); |
| aLine.append( "\n" ); |
| } |
| if( m_aContext.Tagged && m_aContext.Version > PDFWriter::PDF_1_3 ) |
| { |
| aLine.append( "/MarkInfo<</Marked true>>\n" ); |
| } |
| if( m_aWidgets.size() > 0 ) |
| { |
| aLine.append( "/AcroForm<</Fields[\n" ); |
| int nWidgets = m_aWidgets.size(); |
| int nOut = 0; |
| for( int j = 0; j < nWidgets; j++ ) |
| { |
| // output only root fields |
| if( m_aWidgets[j].m_nParent < 1 ) |
| { |
| aLine.append( m_aWidgets[j].m_nObject ); |
| aLine.append( (nOut++ % 5)==4 ? " 0 R\n" : " 0 R " ); |
| } |
| } |
| aLine.append( "\n]/DR " ); |
| aLine.append( getResourceDictObj() ); |
| aLine.append( " 0 R" ); |
| if( m_bIsPDF_A1 ) |
| aLine.append( ">>\n" ); |
| else |
| aLine.append( "/NeedAppearances true>>\n" ); |
| } |
| //--->i59651 |
| //check if there is a Metadata object |
| if( nOutputIntentObject ) |
| { |
| aLine.append("/OutputIntents["); |
| aLine.append( nOutputIntentObject ); |
| aLine.append( " 0 R]" ); |
| } |
| if( nMetadataObject ) |
| { |
| aLine.append("/Metadata "); |
| aLine.append( nMetadataObject ); |
| aLine.append( " 0 R" ); |
| } |
| //<---- |
| aLine.append( ">>\n" |
| "endobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| return true; |
| } |
| |
| sal_Int32 PDFWriterImpl::emitInfoDict( ) |
| { |
| sal_Int32 nObject = createObject(); |
| |
| if( updateObject( nObject ) ) |
| { |
| OStringBuffer aLine( 1024 ); |
| aLine.append( nObject ); |
| aLine.append( " 0 obj\n" |
| "<<" ); |
| if( m_aContext.DocumentInfo.Title.Len() ) |
| { |
| aLine.append( "/Title" ); |
| appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Title, nObject, aLine ); |
| aLine.append( "\n" ); |
| } |
| if( m_aContext.DocumentInfo.Author.Len() ) |
| { |
| aLine.append( "/Author" ); |
| appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, nObject, aLine ); |
| aLine.append( "\n" ); |
| } |
| if( m_aContext.DocumentInfo.Subject.Len() ) |
| { |
| aLine.append( "/Subject" ); |
| appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Subject, nObject, aLine ); |
| aLine.append( "\n" ); |
| } |
| if( m_aContext.DocumentInfo.Keywords.Len() ) |
| { |
| aLine.append( "/Keywords" ); |
| appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Keywords, nObject, aLine ); |
| aLine.append( "\n" ); |
| } |
| if( m_aContext.DocumentInfo.Creator.Len() ) |
| { |
| aLine.append( "/Creator" ); |
| appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Creator, nObject, aLine ); |
| aLine.append( "\n" ); |
| } |
| if( m_aContext.DocumentInfo.Producer.Len() ) |
| { |
| aLine.append( "/Producer" ); |
| appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Producer, nObject, aLine ); |
| aLine.append( "\n" ); |
| } |
| |
| aLine.append( "/CreationDate" ); |
| appendLiteralStringEncrypt( m_aCreationDateString, nObject, aLine ); |
| aLine.append( ">>\nendobj\n\n" ); |
| if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) |
| nObject = 0; |
| } |
| else |
| nObject = 0; |
| |
| return nObject; |
| } |
| |
| //--->i56629 |
| // Part of this function may be shared with method appendDest. |
| // |
| sal_Int32 PDFWriterImpl::emitNamedDestinations() |
| { |
| sal_Int32 nCount = m_aNamedDests.size(); |
| if( nCount <= 0 ) |
| return 0;//define internal error |
| |
| //get the object number for all the destinations |
| sal_Int32 nObject = createObject(); |
| |
| if( updateObject( nObject ) ) |
| { |
| //emit the dictionary |
| OStringBuffer aLine( 1024 ); |
| aLine.append( nObject ); |
| aLine.append( " 0 obj\n" |
| "<<" ); |
| |
| sal_Int32 nDestID; |
| for( nDestID = 0; nDestID < nCount; nDestID++ ) |
| { |
| const PDFNamedDest& rDest = m_aNamedDests[ nDestID ]; |
| // In order to correctly function both under an Internet browser and |
| // directly with a reader (provided the reader has the feature) we |
| // need to set the name of the destination the same way it will be encoded |
| // in an Internet link |
| INetURLObject aLocalURL( |
| OUString( RTL_CONSTASCII_USTRINGPARAM( "http://ahost.ax" ) ) ); //dummy location, won't be used |
| aLocalURL.SetMark( rDest.m_aDestName ); |
| |
| const rtl::OUString aName = aLocalURL.GetMark( INetURLObject::NO_DECODE ); //same coding as |
| // in link creation ( see PDFWriterImpl::emitLinkAnnotations ) |
| const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ]; |
| |
| aLine.append( '/' ); |
| appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog ) |
| aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function |
| //maps the preceeding character properly |
| aLine.append( rDestPage.m_nPageObject ); |
| aLine.append( " 0 R" ); |
| |
| switch( rDest.m_eType ) |
| { |
| case PDFWriter::XYZ: |
| default: |
| aLine.append( "/XYZ " ); |
| appendFixedInt( rDest.m_aRect.Left(), aLine ); |
| aLine.append( ' ' ); |
| appendFixedInt( rDest.m_aRect.Bottom(), aLine ); |
| aLine.append( " 0" ); |
| break; |
| case PDFWriter::Fit: |
| aLine.append( "/Fit" ); |
| break; |
| case PDFWriter::FitRectangle: |
| aLine.append( "/FitR " ); |
| appendFixedInt( rDest.m_aRect.Left(), aLine ); |
| aLine.append( ' ' ); |
| appendFixedInt( rDest.m_aRect.Top(), aLine ); |
| aLine.append( ' ' ); |
| appendFixedInt( rDest.m_aRect.Right(), aLine ); |
| aLine.append( ' ' ); |
| appendFixedInt( rDest.m_aRect.Bottom(), aLine ); |
| break; |
| case PDFWriter::FitHorizontal: |
| aLine.append( "/FitH " ); |
| appendFixedInt( rDest.m_aRect.Bottom(), aLine ); |
| break; |
| case PDFWriter::FitVertical: |
| aLine.append( "/FitV " ); |
| appendFixedInt( rDest.m_aRect.Left(), aLine ); |
| break; |
| case PDFWriter::FitPageBoundingBox: |
| aLine.append( "/FitB" ); |
| break; |
| case PDFWriter::FitPageBoundingBoxHorizontal: |
| aLine.append( "/FitBH " ); |
| appendFixedInt( rDest.m_aRect.Bottom(), aLine ); |
| break; |
| case PDFWriter::FitPageBoundingBoxVertical: |
| aLine.append( "/FitBV " ); |
| appendFixedInt( rDest.m_aRect.Left(), aLine ); |
| break; |
| } |
| aLine.append( "]\n" ); |
| } |
| //close |
| |
| aLine.append( ">>\nendobj\n\n" ); |
| if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) |
| nObject = 0; |
| } |
| else |
| nObject = 0; |
| |
| return nObject; |
| } |
| //<--- i56629 |
| |
| //--->i59651 |
| // emits the output intent dictionary |
| |
| sal_Int32 PDFWriterImpl::emitOutputIntent() |
| { |
| if( !m_bIsPDF_A1 ) |
| return 0; |
| |
| //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1 |
| |
| OStringBuffer aLine( 1024 ); |
| sal_Int32 nICCObject = createObject(); |
| sal_Int32 nStreamLengthObject = createObject(); |
| |
| aLine.append( nICCObject ); |
| // sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16) |
| aLine.append( " 0 obj\n<</N 3/Length " ); |
| aLine.append( nStreamLengthObject ); |
| aLine.append( " 0 R" ); |
| #ifndef DEBUG_DISABLE_PDFCOMPRESSION |
| aLine.append( "/Filter/FlateDecode" ); |
| #endif |
| aLine.append( ">>\nstream\n" ); |
| CHECK_RETURN( updateObject( nICCObject ) ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| //get file position |
| sal_uInt64 nBeginStreamPos = 0; |
| osl_getFilePos( m_aFile, &nBeginStreamPos ); |
| beginCompression(); |
| checkAndEnableStreamEncryption( nICCObject ); |
| sal_Int32 nStreamSize = writeBuffer( nsRGB_ICC_profile, (sal_Int32) sizeof( nsRGB_ICC_profile ) ); |
| disableStreamEncryption(); |
| endCompression(); |
| sal_uInt64 nEndStreamPos = 0; |
| osl_getFilePos( m_aFile, &nEndStreamPos ); |
| |
| if( nStreamSize == 0 ) |
| return 0; |
| if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) ) |
| return 0 ; |
| aLine.setLength( 0 ); |
| |
| //emit the stream length object |
| CHECK_RETURN( updateObject( nStreamLengthObject ) ); |
| aLine.setLength( 0 ); |
| aLine.append( nStreamLengthObject ); |
| aLine.append( " 0 obj\n" ); |
| aLine.append( (sal_Int64)(nEndStreamPos-nBeginStreamPos) ); |
| aLine.append( "\nendobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| aLine.setLength( 0 ); |
| |
| //emit the OutputIntent dictionary |
| sal_Int32 nOIObject = createObject(); |
| CHECK_RETURN( updateObject( nOIObject ) ); |
| aLine.append( nOIObject ); |
| aLine.append( " 0 obj\n" |
| "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier"); |
| |
| rtl::OUString aComment( RTL_CONSTASCII_USTRINGPARAM( "sRGB IEC61966-2.1" ) ); |
| appendLiteralStringEncrypt( aComment ,nOIObject, aLine ); |
| aLine.append("/DestOutputProfile "); |
| aLine.append( nICCObject ); |
| aLine.append( " 0 R>>\nendobj\n\n" );; |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| return nOIObject; |
| } |
| |
| // formats the string for the XML stream |
| static void escapeStringXML( const rtl::OUString& rStr, rtl::OUString &rValue) |
| { |
| const sal_Unicode* pUni = rStr.getStr(); |
| int nLen = rStr.getLength(); |
| for( ; nLen; nLen--, pUni++ ) |
| { |
| switch( *pUni ) |
| { |
| case sal_Unicode('&'): |
| rValue += rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "&" ) ); |
| break; |
| case sal_Unicode('<'): |
| rValue += rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "<" ) ); |
| break; |
| case sal_Unicode('>'): |
| rValue += rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( ">" ) ); |
| break; |
| case sal_Unicode('\''): |
| rValue += rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "'" ) ); |
| break; |
| case sal_Unicode('"'): |
| rValue += rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( """ ) ); |
| break; |
| default: |
| rValue += rtl::OUString( *pUni ); |
| break; |
| } |
| } |
| } |
| |
| // emits the document metadata |
| // |
| sal_Int32 PDFWriterImpl::emitDocumentMetadata() |
| { |
| if( !m_bIsPDF_A1 ) |
| return 0; |
| |
| //get the object number for all the destinations |
| sal_Int32 nObject = createObject(); |
| |
| if( updateObject( nObject ) ) |
| { |
| // the following string are written in UTF-8 unicode |
| OStringBuffer aMetadataStream( 8192 ); |
| |
| aMetadataStream.append( "<?xpacket begin=\"" ); |
| // this lines writes Unicode “zero width non-breaking space character” (U+FEFF) (aka byte-order mark ) used |
| // as a byte-order marker. |
| aMetadataStream.append( OUStringToOString( OUString( sal_Unicode( 0xFEFF ) ), RTL_TEXTENCODING_UTF8 ) ); |
| aMetadataStream.append( "\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n" ); |
| aMetadataStream.append( "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\">\n" ); |
| aMetadataStream.append( " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" ); |
| //PDF/A part ( ISO 19005-1:2005 - 6.7.11 ) |
| aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" ); |
| aMetadataStream.append( " xmlns:pdfaid=\"http://www.aiim.org/pdfa/ns/id/\">\n" ); |
| aMetadataStream.append( " <pdfaid:part>1</pdfaid:part>\n" ); |
| aMetadataStream.append( " <pdfaid:conformance>A</pdfaid:conformance>\n" ); |
| aMetadataStream.append( " </rdf:Description>\n" ); |
| //... Dublin Core properties go here |
| if( m_aContext.DocumentInfo.Title.Len() || |
| m_aContext.DocumentInfo.Author.Len() || |
| m_aContext.DocumentInfo.Subject.Len() ) |
| { |
| aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" ); |
| aMetadataStream.append( " xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n" ); |
| if( m_aContext.DocumentInfo.Title.Len() ) |
| { |
| // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01) |
| aMetadataStream.append( " <dc:title>\n" ); |
| aMetadataStream.append( " <rdf:Alt>\n" ); |
| aMetadataStream.append( " <rdf:li xml:lang=\"x-default\">" ); |
| rtl::OUString aTitle; |
| escapeStringXML( m_aContext.DocumentInfo.Title, aTitle ); |
| aMetadataStream.append( OUStringToOString( aTitle, RTL_TEXTENCODING_UTF8 ) ); |
| aMetadataStream.append( "</rdf:li>\n" ); |
| aMetadataStream.append( " </rdf:Alt>\n" ); |
| aMetadataStream.append( " </dc:title>\n" ); |
| } |
| if( m_aContext.DocumentInfo.Author.Len() ) |
| { |
| aMetadataStream.append( " <dc:creator>\n" ); |
| aMetadataStream.append( " <rdf:Seq>\n" ); |
| aMetadataStream.append( " <rdf:li>" ); |
| rtl::OUString aAuthor; |
| escapeStringXML( m_aContext.DocumentInfo.Author, aAuthor ); |
| aMetadataStream.append( OUStringToOString( aAuthor , RTL_TEXTENCODING_UTF8 ) ); |
| aMetadataStream.append( "</rdf:li>\n" ); |
| aMetadataStream.append( " </rdf:Seq>\n" ); |
| aMetadataStream.append( " </dc:creator>\n" ); |
| } |
| if( m_aContext.DocumentInfo.Subject.Len() ) |
| { |
| // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01) |
| aMetadataStream.append( " <dc:description>\n" ); |
| aMetadataStream.append( " <rdf:Alt>\n" ); |
| aMetadataStream.append( " <rdf:li xml:lang=\"x-default\">" ); |
| rtl::OUString aSubject; |
| escapeStringXML( m_aContext.DocumentInfo.Subject, aSubject ); |
| aMetadataStream.append( OUStringToOString( aSubject , RTL_TEXTENCODING_UTF8 ) ); |
| aMetadataStream.append( "</rdf:li>\n" ); |
| aMetadataStream.append( " </rdf:Alt>\n" ); |
| aMetadataStream.append( " </dc:description>\n" ); |
| } |
| aMetadataStream.append( " </rdf:Description>\n" ); |
| } |
| |
| //... PDF properties go here |
| if( m_aContext.DocumentInfo.Producer.Len() || |
| m_aContext.DocumentInfo.Keywords.Len() ) |
| { |
| aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" ); |
| aMetadataStream.append( " xmlns:pdf=\"http://ns.adobe.com/pdf/1.3/\">\n" ); |
| if( m_aContext.DocumentInfo.Producer.Len() ) |
| { |
| aMetadataStream.append( " <pdf:Producer>" ); |
| rtl::OUString aProducer; |
| escapeStringXML( m_aContext.DocumentInfo.Producer, aProducer ); |
| aMetadataStream.append( OUStringToOString( aProducer , RTL_TEXTENCODING_UTF8 ) ); |
| aMetadataStream.append( "</pdf:Producer>\n" ); |
| } |
| if( m_aContext.DocumentInfo.Keywords.Len() ) |
| { |
| aMetadataStream.append( " <pdf:Keywords>" ); |
| rtl::OUString aKeywords; |
| escapeStringXML( m_aContext.DocumentInfo.Keywords, aKeywords ); |
| aMetadataStream.append( OUStringToOString( aKeywords , RTL_TEXTENCODING_UTF8 ) ); |
| aMetadataStream.append( "</pdf:Keywords>\n" ); |
| } |
| aMetadataStream.append( " </rdf:Description>\n" ); |
| } |
| |
| aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" ); |
| aMetadataStream.append( " xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\">\n" ); |
| if( m_aContext.DocumentInfo.Creator.Len() ) |
| { |
| aMetadataStream.append( " <xmp:CreatorTool>" ); |
| rtl::OUString aCreator; |
| escapeStringXML( m_aContext.DocumentInfo.Creator, aCreator ); |
| aMetadataStream.append( OUStringToOString( aCreator , RTL_TEXTENCODING_UTF8 ) ); |
| aMetadataStream.append( "</xmp:CreatorTool>\n" ); |
| } |
| //creation date |
| aMetadataStream.append( " <xmp:CreateDate>" ); |
| aMetadataStream.append( m_aCreationMetaDateString ); |
| aMetadataStream.append( "</xmp:CreateDate>\n" ); |
| |
| aMetadataStream.append( " </rdf:Description>\n" ); |
| aMetadataStream.append( " </rdf:RDF>\n" ); |
| aMetadataStream.append( "</x:xmpmeta>\n" ); |
| |
| //add the padding |
| for( sal_Int32 nSpaces = 1; nSpaces <= 2100; nSpaces++ ) |
| { |
| aMetadataStream.append( " " ); |
| if( nSpaces % 100 == 0 ) |
| aMetadataStream.append( "\n" ); |
| } |
| |
| aMetadataStream.append( "<?xpacket end=\"w\"?>\n" ); |
| |
| OStringBuffer aMetadataObj( 1024 ); |
| |
| aMetadataObj.append( nObject ); |
| aMetadataObj.append( " 0 obj\n" ); |
| |
| aMetadataObj.append( "<</Type/Metadata/Subtype/XML/Length " ); |
| |
| aMetadataObj.append( (sal_Int32) aMetadataStream.getLength() ); |
| aMetadataObj.append( ">>\nstream\n" ); |
| CHECK_RETURN( writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) ); |
| //emit the stream |
| CHECK_RETURN( writeBuffer( aMetadataStream.getStr(), aMetadataStream.getLength() ) ); |
| |
| aMetadataObj.setLength( 0 ); |
| aMetadataObj.append( "\nendstream\nendobj\n\n" ); |
| if( ! writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) ) |
| nObject = 0; |
| } |
| else |
| nObject = 0; |
| |
| return nObject; |
| } |
| //<---i59651 |
| |
| bool PDFWriterImpl::emitTrailer() |
| { |
| // emit doc info |
| OString aInfoValuesOut; |
| sal_Int32 nDocInfoObject = emitInfoDict( ); |
| |
| sal_Int32 nSecObject = 0; |
| |
| if( m_aContext.Encryption.Encrypt() ) |
| { |
| //emit the security information |
| //must be emitted as indirect dictionary object, since |
| //Acrobat Reader 5 works only with this kind of implementation |
| nSecObject = createObject(); |
| |
| if( updateObject( nSecObject ) ) |
| { |
| OStringBuffer aLineS( 1024 ); |
| aLineS.append( nSecObject ); |
| aLineS.append( " 0 obj\n" |
| "<</Filter/Standard/V " ); |
| // check the version |
| if( m_aContext.Encryption.Security128bit ) |
| aLineS.append( "2/Length 128/R 3" ); |
| else |
| aLineS.append( "1/R 2" ); |
| |
| // emit the owner password, must not be encrypted |
| aLineS.append( "/O(" ); |
| appendLiteralString( (const sal_Char*)&m_aContext.Encryption.OValue[0], sal_Int32(m_aContext.Encryption.OValue.size()), aLineS ); |
| aLineS.append( ")/U(" ); |
| appendLiteralString( (const sal_Char*)&m_aContext.Encryption.UValue[0], sal_Int32(m_aContext.Encryption.UValue.size()), aLineS ); |
| aLineS.append( ")/P " );// the permission set |
| aLineS.append( m_nAccessPermissions ); |
| aLineS.append( ">>\nendobj\n\n" ); |
| if( !writeBuffer( aLineS.getStr(), aLineS.getLength() ) ) |
| nSecObject = 0; |
| } |
| else |
| nSecObject = 0; |
| } |
| // emit xref table |
| // remember start |
| sal_uInt64 nXRefOffset = 0; |
| CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nXRefOffset )) ); |
| CHECK_RETURN( writeBuffer( "xref\n", 5 ) ); |
| |
| sal_Int32 nObjects = m_aObjects.size(); |
| OStringBuffer aLine; |
| aLine.append( "0 " ); |
| aLine.append( (sal_Int32)(nObjects+1) ); |
| aLine.append( "\n" ); |
| aLine.append( "0000000000 65535 f \n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| for( sal_Int32 i = 0; i < nObjects; i++ ) |
| { |
| aLine.setLength( 0 ); |
| OString aOffset = OString::valueOf( (sal_Int64)m_aObjects[i] ); |
| for( sal_Int32 j = 0; j < (10-aOffset.getLength()); j++ ) |
| aLine.append( '0' ); |
| aLine.append( aOffset ); |
| aLine.append( " 00000 n \n" ); |
| DBG_ASSERT( aLine.getLength() == 20, "invalid xref entry" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| } |
| |
| // prepare document checksum |
| OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 ); |
| if( m_aDocDigest ) |
| { |
| sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ]; |
| rtl_digest_getMD5( m_aDocDigest, nMD5Sum, sizeof(nMD5Sum) ); |
| for( unsigned int i = 0; i < RTL_DIGEST_LENGTH_MD5; i++ ) |
| appendHex( nMD5Sum[i], aDocChecksum ); |
| } |
| // document id set in setDocInfo method |
| // emit trailer |
| aLine.setLength( 0 ); |
| aLine.append( "trailer\n" |
| "<</Size " ); |
| aLine.append( (sal_Int32)(nObjects+1) ); |
| aLine.append( "/Root " ); |
| aLine.append( m_nCatalogObject ); |
| aLine.append( " 0 R\n" ); |
| if( nSecObject ) |
| { |
| aLine.append( "/Encrypt "); |
| aLine.append( nSecObject ); |
| aLine.append( " 0 R\n" ); |
| } |
| if( nDocInfoObject ) |
| { |
| aLine.append( "/Info " ); |
| aLine.append( nDocInfoObject ); |
| aLine.append( " 0 R\n" ); |
| } |
| if( ! m_aContext.Encryption.DocumentIdentifier.empty() ) |
| { |
| aLine.append( "/ID [ <" ); |
| for( std::vector< sal_uInt8 >::const_iterator it = m_aContext.Encryption.DocumentIdentifier.begin(); |
| it != m_aContext.Encryption.DocumentIdentifier.end(); ++it ) |
| { |
| appendHex( sal_Int8(*it), aLine ); |
| } |
| aLine.append( ">\n" |
| "<" ); |
| for( std::vector< sal_uInt8 >::const_iterator it = m_aContext.Encryption.DocumentIdentifier.begin(); |
| it != m_aContext.Encryption.DocumentIdentifier.end(); ++it ) |
| { |
| appendHex( sal_Int8(*it), aLine ); |
| } |
| aLine.append( "> ]\n" ); |
| } |
| if( aDocChecksum.getLength() ) |
| { |
| aLine.append( "/DocChecksum /" ); |
| aLine.append( aDocChecksum ); |
| aLine.append( "\n" ); |
| } |
| if( m_aAdditionalStreams.size() > 0 ) |
| { |
| aLine.append( "/AdditionalStreams [" ); |
| for( unsigned int i = 0; i < m_aAdditionalStreams.size(); i++ ) |
| { |
| aLine.append( "/" ); |
| appendName( m_aAdditionalStreams[i].m_aMimeType, aLine ); |
| aLine.append( " " ); |
| aLine.append( m_aAdditionalStreams[i].m_nStreamObject ); |
| aLine.append( " 0 R\n" ); |
| } |
| aLine.append( "]\n" ); |
| } |
| aLine.append( ">>\n" |
| "startxref\n" ); |
| aLine.append( (sal_Int64)nXRefOffset ); |
| aLine.append( "\n" |
| "%%EOF\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| return true; |
| } |
| |
| struct AnnotationSortEntry |
| { |
| sal_Int32 nTabOrder; |
| sal_Int32 nObject; |
| sal_Int32 nWidgetIndex; |
| |
| AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) : |
| nTabOrder( nTab ), |
| nObject( nObj ), |
| nWidgetIndex( nI ) |
| {} |
| }; |
| |
| struct AnnotSortContainer |
| { |
| std::set< sal_Int32 > aObjects; |
| std::vector< AnnotationSortEntry > aSortedAnnots; |
| }; |
| |
| struct AnnotSorterLess |
| { |
| std::vector< PDFWriterImpl::PDFWidget >& m_rWidgets; |
| |
| AnnotSorterLess( std::vector< PDFWriterImpl::PDFWidget >& rWidgets ) : m_rWidgets( rWidgets ) {} |
| |
| bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight ) |
| { |
| if( rLeft.nTabOrder < rRight.nTabOrder ) |
| return true; |
| if( rRight.nTabOrder < rLeft.nTabOrder ) |
| return false; |
| if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 ) |
| return false; |
| if( rRight.nWidgetIndex < 0 ) |
| return true; |
| if( rLeft.nWidgetIndex < 0 ) |
| return false; |
| // remember: widget rects are in PDF coordinates, so they are ordered down up |
| if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() > |
| m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() ) |
| return true; |
| if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() > |
| m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() ) |
| return false; |
| if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() < |
| m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() ) |
| return true; |
| return false; |
| } |
| }; |
| |
| void PDFWriterImpl::sortWidgets() |
| { |
| // sort widget annotations on each page as per their |
| // TabOrder attribute |
| std::hash_map< sal_Int32, AnnotSortContainer > sorted; |
| int nWidgets = m_aWidgets.size(); |
| for( int nW = 0; nW < nWidgets; nW++ ) |
| { |
| const PDFWidget& rWidget = m_aWidgets[nW]; |
| if( rWidget.m_nPage >= 0 ) |
| { |
| AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ]; |
| // optimize vector allocation |
| if( rCont.aSortedAnnots.empty() ) |
| rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() ); |
| // insert widget to tab sorter |
| // RadioButtons are not page annotations, only their individual check boxes are |
| if( rWidget.m_eType != PDFWriter::RadioButton ) |
| { |
| rCont.aObjects.insert( rWidget.m_nObject ); |
| rCont.aSortedAnnots.push_back( AnnotationSortEntry( rWidget.m_nTabOrder, rWidget.m_nObject, nW ) ); |
| } |
| } |
| } |
| for( std::hash_map< sal_Int32, AnnotSortContainer >::iterator it = sorted.begin(); it != sorted.end(); ++it ) |
| { |
| // append entries for non widget annotations |
| PDFPage& rPage = m_aPages[ it->first ]; |
| unsigned int nAnnots = rPage.m_aAnnotations.size(); |
| for( unsigned int nA = 0; nA < nAnnots; nA++ ) |
| if( it->second.aObjects.find( rPage.m_aAnnotations[nA] ) == it->second.aObjects.end()) |
| it->second.aSortedAnnots.push_back( AnnotationSortEntry( 10000, rPage.m_aAnnotations[nA], -1 ) ); |
| |
| AnnotSorterLess aLess( m_aWidgets ); |
| std::stable_sort( it->second.aSortedAnnots.begin(), it->second.aSortedAnnots.end(), aLess ); |
| // sanity check |
| if( it->second.aSortedAnnots.size() == nAnnots) |
| { |
| for( unsigned int nA = 0; nA < nAnnots; nA++ ) |
| rPage.m_aAnnotations[nA] = it->second.aSortedAnnots[nA].nObject; |
| } |
| else |
| { |
| DBG_ASSERT( 0, "wrong number of sorted annotations" ); |
| #if OSL_DEBUG_LEVEL > 0 |
| fprintf( stderr, "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions on page nr %ld\n" |
| " %ld sorted and %ld unsorted\n", (long int)it->first, (long int)it->second.aSortedAnnots.size(), (long int)nAnnots ); |
| #endif |
| } |
| } |
| |
| // FIXME: implement tab order in structure tree for PDF 1.5 |
| } |
| |
| namespace vcl { |
| class PDFStreamIf : |
| public cppu::WeakImplHelper1< com::sun::star::io::XOutputStream > |
| { |
| PDFWriterImpl* m_pWriter; |
| bool m_bWrite; |
| public: |
| PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {} |
| virtual ~PDFStreamIf(); |
| |
| virtual void SAL_CALL writeBytes( const com::sun::star::uno::Sequence< sal_Int8 >& aData ) throw(); |
| virtual void SAL_CALL flush() throw(); |
| virtual void SAL_CALL closeOutput() throw(); |
| }; |
| } |
| |
| PDFStreamIf::~PDFStreamIf() |
| { |
| } |
| |
| void SAL_CALL PDFStreamIf::writeBytes( const com::sun::star::uno::Sequence< sal_Int8 >& aData ) throw() |
| { |
| if( m_bWrite ) |
| { |
| sal_Int32 nBytes = aData.getLength(); |
| if( nBytes > 0 ) |
| m_pWriter->writeBuffer( aData.getConstArray(), nBytes ); |
| } |
| } |
| |
| void SAL_CALL PDFStreamIf::flush() throw() |
| { |
| } |
| |
| void SAL_CALL PDFStreamIf::closeOutput() throw() |
| { |
| m_bWrite = false; |
| } |
| |
| bool PDFWriterImpl::emitAdditionalStreams() |
| { |
| unsigned int nStreams = m_aAdditionalStreams.size(); |
| for( unsigned int i = 0; i < nStreams; i++ ) |
| { |
| PDFAddStream& rStream = m_aAdditionalStreams[i]; |
| rStream.m_nStreamObject = createObject(); |
| sal_Int32 nSizeObject = createObject(); |
| |
| if( ! updateObject( rStream.m_nStreamObject ) ) |
| return false; |
| |
| OStringBuffer aLine; |
| aLine.append( rStream.m_nStreamObject ); |
| aLine.append( " 0 obj\n<</Length " ); |
| aLine.append( nSizeObject ); |
| aLine.append( " 0 R" ); |
| if( rStream.m_bCompress ) |
| aLine.append( "/Filter/FlateDecode" ); |
| aLine.append( ">>\nstream\n" ); |
| if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) |
| return false; |
| sal_uInt64 nBeginStreamPos = 0, nEndStreamPos = 0; |
| if( osl_File_E_None != osl_getFilePos( m_aFile, &nBeginStreamPos ) ) |
| { |
| osl_closeFile( m_aFile ); |
| m_bOpen = false; |
| } |
| if( rStream.m_bCompress ) |
| beginCompression(); |
| |
| checkAndEnableStreamEncryption( rStream.m_nStreamObject ); |
| com::sun::star::uno::Reference< com::sun::star::io::XOutputStream > xStream( new PDFStreamIf( this ) ); |
| rStream.m_pStream->write( xStream ); |
| xStream.clear(); |
| delete rStream.m_pStream; |
| rStream.m_pStream = NULL; |
| disableStreamEncryption(); |
| |
| if( rStream.m_bCompress ) |
| endCompression(); |
| |
| if( osl_File_E_None != osl_getFilePos( m_aFile, &nEndStreamPos ) ) |
| { |
| osl_closeFile( m_aFile ); |
| m_bOpen = false; |
| return false; |
| } |
| if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) ) |
| return false ; |
| // emit stream length object |
| if( ! updateObject( nSizeObject ) ) |
| return false; |
| aLine.setLength( 0 ); |
| aLine.append( nSizeObject ); |
| aLine.append( " 0 obj\n" ); |
| aLine.append( (sal_Int64)(nEndStreamPos-nBeginStreamPos) ); |
| aLine.append( "\nendobj\n\n" ); |
| if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) |
| return false; |
| } |
| return true; |
| } |
| |
| bool PDFWriterImpl::emit() |
| { |
| endPage(); |
| |
| // resort structure tree and annotations if necessary |
| // needed for widget tab order |
| sortWidgets(); |
| |
| // emit additional streams |
| CHECK_RETURN( emitAdditionalStreams() ); |
| |
| // emit catalog |
| CHECK_RETURN( emitCatalog() ); |
| |
| // emit trailer |
| CHECK_RETURN( emitTrailer() ); |
| |
| osl_closeFile( m_aFile ); |
| m_bOpen = false; |
| |
| return true; |
| } |
| |
| std::set< PDFWriter::ErrorCode > PDFWriterImpl::getErrors() |
| { |
| return m_aErrors; |
| } |
| |
| sal_Int32 PDFWriterImpl::getSystemFont( const Font& i_rFont ) |
| { |
| getReferenceDevice()->Push(); |
| getReferenceDevice()->SetFont( i_rFont ); |
| getReferenceDevice()->ImplNewFont(); |
| |
| const ImplFontData* pDevFont = m_pReferenceDevice->mpFontEntry->maFontSelData.mpFontData; |
| sal_Int32 nFontID = 0; |
| FontEmbedData::iterator it = m_aSystemFonts.find( pDevFont ); |
| if( it != m_aSystemFonts.end() ) |
| nFontID = it->second.m_nNormalFontID; |
| else |
| { |
| nFontID = m_nNextFID++; |
| m_aSystemFonts[ pDevFont ] = EmbedFont(); |
| m_aSystemFonts[ pDevFont ].m_nNormalFontID = nFontID; |
| } |
| |
| getReferenceDevice()->Pop(); |
| getReferenceDevice()->ImplNewFont(); |
| |
| return nFontID; |
| } |
| |
| void PDFWriterImpl::registerGlyphs( int nGlyphs, |
| sal_GlyphId* pGlyphs, |
| sal_Int32* pGlyphWidths, |
| sal_Ucs* pUnicodes, |
| sal_Int32* pUnicodesPerGlyph, |
| sal_uInt8* pMappedGlyphs, |
| sal_Int32* pMappedFontObjects, |
| const ImplFontData* pFallbackFonts[] ) |
| { |
| const ImplFontData* pDevFont = m_pReferenceDevice->mpFontEntry->maFontSelData.mpFontData; |
| sal_Ucs* pCurUnicode = pUnicodes; |
| for( int i = 0; i < nGlyphs; pCurUnicode += pUnicodesPerGlyph[i] , i++ ) |
| { |
| const int nFontGlyphId = pGlyphs[i] & (GF_IDXMASK | GF_ISCHAR | GF_GSUB); |
| const ImplFontData* pCurrentFont = pFallbackFonts[i] ? pFallbackFonts[i] : pDevFont; |
| |
| if( isBuiltinFont( pCurrentFont ) ) |
| { |
| sal_Int32 nFontID = 0; |
| FontEmbedData::iterator it = m_aEmbeddedFonts.find( pCurrentFont ); |
| if( it != m_aEmbeddedFonts.end() ) |
| nFontID = it->second.m_nNormalFontID; |
| else |
| { |
| nFontID = m_nNextFID++; |
| m_aEmbeddedFonts[ pCurrentFont ] = EmbedFont(); |
| m_aEmbeddedFonts[ pCurrentFont ].m_nNormalFontID = nFontID; |
| } |
| |
| pGlyphWidths[ i ] = 0; |
| pMappedGlyphs[ i ] = sal::static_int_cast<sal_Int8>( nFontGlyphId ); |
| pMappedFontObjects[ i ] = nFontID; |
| const ImplPdfBuiltinFontData* pFD = GetPdfFontData( pCurrentFont ); |
| if( pFD ) |
| { |
| const BuiltinFont* pBuiltinFont = pFD->GetBuiltinFont(); |
| pGlyphWidths[i] = pBuiltinFont->m_aWidths[ nFontGlyphId & 0x00ff ]; |
| } |
| } |
| else if( pCurrentFont->mbSubsettable ) |
| { |
| FontSubset& rSubset = m_aSubsets[ pCurrentFont ]; |
| // search for font specific glyphID |
| FontMapping::iterator it = rSubset.m_aMapping.find( nFontGlyphId ); |
| if( it != rSubset.m_aMapping.end() ) |
| { |
| pMappedFontObjects[i] = it->second.m_nFontID; |
| pMappedGlyphs[i] = it->second.m_nSubsetGlyphID; |
| } |
| else |
| { |
| // create new subset if necessary |
| if( rSubset.m_aSubsets.empty() |
| || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) ) |
| { |
| rSubset.m_aSubsets.push_back( FontEmit( m_nNextFID++ ) ); |
| } |
| |
| // copy font id |
| pMappedFontObjects[i] = rSubset.m_aSubsets.back().m_nFontID; |
| // create new glyph in subset |
| sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1); |
| pMappedGlyphs[i] = nNewId; |
| |
| // add new glyph to emitted font subset |
| GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ]; |
| rNewGlyphEmit.setGlyphId( nNewId ); |
| for( sal_Int32 n = 0; n < pUnicodesPerGlyph[i]; n++ ) |
| rNewGlyphEmit.addCode( pCurUnicode[n] ); |
| |
| // add new glyph to font mapping |
| Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ]; |
| rNewGlyph.m_nFontID = pMappedFontObjects[i]; |
| rNewGlyph.m_nSubsetGlyphID = nNewId; |
| } |
| getReferenceDevice()->ImplGetGraphics(); |
| const bool bVertical = ((pGlyphs[i] & GF_ROTMASK) != 0); |
| pGlyphWidths[i] = m_aFontCache.getGlyphWidth( pCurrentFont, |
| nFontGlyphId, |
| bVertical, |
| m_pReferenceDevice->mpGraphics ); |
| } |
| else if( pCurrentFont->IsEmbeddable() ) |
| { |
| sal_Int32 nFontID = 0; |
| FontEmbedData::iterator it = m_aEmbeddedFonts.find( pCurrentFont ); |
| if( it != m_aEmbeddedFonts.end() ) |
| nFontID = it->second.m_nNormalFontID; |
| else |
| { |
| nFontID = m_nNextFID++; |
| m_aEmbeddedFonts[ pCurrentFont ] = EmbedFont(); |
| m_aEmbeddedFonts[ pCurrentFont ].m_nNormalFontID = nFontID; |
| } |
| EmbedFont& rEmbedFont = m_aEmbeddedFonts[pCurrentFont]; |
| |
| const Ucs2SIntMap* pEncoding = NULL; |
| const Ucs2OStrMap* pNonEncoded = NULL; |
| getReferenceDevice()->ImplGetGraphics(); |
| pEncoding = m_pReferenceDevice->mpGraphics->GetFontEncodingVector( pCurrentFont, &pNonEncoded ); |
| |
| Ucs2SIntMap::const_iterator enc_it; |
| Ucs2OStrMap::const_iterator nonenc_it; |
| |
| sal_Int32 nCurFontID = nFontID; |
| sal_Ucs cChar = *pCurUnicode; |
| if( pEncoding ) |
| { |
| enc_it = pEncoding->find( cChar ); |
| if( enc_it != pEncoding->end() && enc_it->second > 0 ) |
| { |
| DBG_ASSERT( (enc_it->second & 0xffffff00) == 0, "Invalid character code" ); |
| cChar = (sal_Ucs)enc_it->second; |
| } |
| else if( (enc_it == pEncoding->end() || enc_it->second == -1) && |
| pNonEncoded && |
| (nonenc_it = pNonEncoded->find( cChar )) != pNonEncoded->end() ) |
| { |
| nCurFontID = 0; |
| // find non encoded glyph |
| for( std::list< EmbedEncoding >::iterator nec_it = rEmbedFont.m_aExtendedEncodings.begin(); nec_it != rEmbedFont.m_aExtendedEncodings.end(); ++nec_it ) |
| { |
| if( nec_it->m_aCMap.find( cChar ) != nec_it->m_aCMap.end() ) |
| { |
| nCurFontID = nec_it->m_nFontID; |
| cChar = (sal_Ucs)nec_it->m_aCMap[ cChar ]; |
| break; |
| } |
| } |
| if( nCurFontID == 0 ) // new nonencoded glyph |
| { |
| if( rEmbedFont.m_aExtendedEncodings.empty() || rEmbedFont.m_aExtendedEncodings.back().m_aEncVector.size() == 255 ) |
| { |
| rEmbedFont.m_aExtendedEncodings.push_back( EmbedEncoding() ); |
| rEmbedFont.m_aExtendedEncodings.back().m_nFontID = m_nNextFID++; |
| } |
| EmbedEncoding& rEncoding = rEmbedFont.m_aExtendedEncodings.back(); |
| rEncoding.m_aEncVector.push_back( EmbedCode() ); |
| rEncoding.m_aEncVector.back().m_aUnicode = cChar; |
| rEncoding.m_aEncVector.back().m_aName = nonenc_it->second; |
| rEncoding.m_aCMap[ cChar ] = (sal_Int8)(rEncoding.m_aEncVector.size()-1); |
| nCurFontID = rEncoding.m_nFontID; |
| cChar = (sal_Ucs)rEncoding.m_aCMap[ cChar ]; |
| } |
| } |
| else |
| pEncoding = NULL; |
| } |
| if( ! pEncoding ) |
| { |
| if( cChar & 0xff00 ) |
| { |
| // some characters can be used by conversion |
| if( cChar >= 0xf000 && cChar <= 0xf0ff ) // symbol encoding in private use area |
| cChar -= 0xf000; |
| else |
| { |
| String aString(cChar); |
| ByteString aChar( aString, RTL_TEXTENCODING_MS_1252 ); |
| cChar = ((sal_Ucs)aChar.GetChar( 0 )) & 0x00ff; |
| } |
| } |
| } |
| |
| pMappedGlyphs[ i ] = (sal_Int8)cChar; |
| pMappedFontObjects[ i ] = nCurFontID; |
| pGlyphWidths[ i ] = m_aFontCache.getGlyphWidth( pCurrentFont, |
| (pEncoding ? *pCurUnicode : cChar) | GF_ISCHAR, |
| false, |
| m_pReferenceDevice->mpGraphics ); |
| } |
| } |
| } |
| |
| void PDFWriterImpl::drawRelief( SalLayout& rLayout, const String& rText, bool bTextLines ) |
| { |
| push( PUSH_ALL ); |
| |
| FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief(); |
| |
| Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor(); |
| Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor; |
| Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor; |
| Color aReliefColor( COL_LIGHTGRAY ); |
| if( aTextColor == COL_BLACK ) |
| aTextColor = Color( COL_WHITE ); |
| if( aTextLineColor == COL_BLACK ) |
| aTextLineColor = Color( COL_WHITE ); |
| if( aOverlineColor == COL_BLACK ) |
| aOverlineColor = Color( COL_WHITE ); |
| if( aTextColor == COL_WHITE ) |
| aReliefColor = Color( COL_BLACK ); |
| |
| Font aSetFont = m_aCurrentPDFState.m_aFont; |
| aSetFont.SetRelief( RELIEF_NONE ); |
| aSetFont.SetShadow( sal_False ); |
| |
| aSetFont.SetColor( aReliefColor ); |
| setTextLineColor( aReliefColor ); |
| setOverlineColor( aReliefColor ); |
| setFont( aSetFont ); |
| long nOff = 1 + getReferenceDevice()->mnDPIX/300; |
| if( eRelief == RELIEF_ENGRAVED ) |
| nOff = -nOff; |
| |
| rLayout.DrawOffset() += Point( nOff, nOff ); |
| updateGraphicsState(); |
| drawLayout( rLayout, rText, bTextLines ); |
| |
| rLayout.DrawOffset() -= Point( nOff, nOff ); |
| setTextLineColor( aTextLineColor ); |
| setOverlineColor( aOverlineColor ); |
| aSetFont.SetColor( aTextColor ); |
| setFont( aSetFont ); |
| updateGraphicsState(); |
| drawLayout( rLayout, rText, bTextLines ); |
| |
| // clean up the mess |
| pop(); |
| } |
| |
| void PDFWriterImpl::drawShadow( SalLayout& rLayout, const String& rText, bool bTextLines ) |
| { |
| Font aSaveFont = m_aCurrentPDFState.m_aFont; |
| Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor; |
| Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor; |
| |
| Font& rFont = m_aCurrentPDFState.m_aFont; |
| if( rFont.GetColor() == Color( COL_BLACK ) || rFont.GetColor().GetLuminance() < 8 ) |
| rFont.SetColor( Color( COL_LIGHTGRAY ) ); |
| else |
| rFont.SetColor( Color( COL_BLACK ) ); |
| rFont.SetShadow( sal_False ); |
| rFont.SetOutline( sal_False ); |
| setFont( rFont ); |
| setTextLineColor( rFont.GetColor() ); |
| setOverlineColor( rFont.GetColor() ); |
| updateGraphicsState(); |
| |
| long nOff = 1 + ((m_pReferenceDevice->mpFontEntry->mnLineHeight-24)/24); |
| if( rFont.IsOutline() ) |
| nOff++; |
| rLayout.DrawBase() += Point( nOff, nOff ); |
| drawLayout( rLayout, rText, bTextLines ); |
| rLayout.DrawBase() -= Point( nOff, nOff ); |
| |
| setFont( aSaveFont ); |
| setTextLineColor( aSaveTextLineColor ); |
| setOverlineColor( aSaveOverlineColor ); |
| updateGraphicsState(); |
| } |
| |
| void PDFWriterImpl::drawVerticalGlyphs( |
| const std::vector<PDFWriterImpl::PDFGlyph>& rGlyphs, |
| OStringBuffer& rLine, |
| const Point& rAlignOffset, |
| const Matrix3& rRotScale, |
| double fAngle, |
| double fXScale, |
| double fSkew, |
| sal_Int32 nFontHeight ) |
| { |
| long nXOffset = 0; |
| Point aCurPos( rGlyphs[0].m_aPos ); |
| aCurPos = m_pReferenceDevice->PixelToLogic( aCurPos ); |
| aCurPos += rAlignOffset; |
| for( size_t i = 0; i < rGlyphs.size(); i++ ) |
| { |
| // have to emit each glyph on its own |
| double fDeltaAngle = 0.0; |
| double fYScale = 1.0; |
| double fTempXScale = fXScale; |
| double fSkewB = fSkew; |
| double fSkewA = 0.0; |
| |
| Point aDeltaPos; |
| if( ( rGlyphs[i].m_nGlyphId & GF_ROTMASK ) == GF_ROTL ) |
| { |
| fDeltaAngle = M_PI/2.0; |
| aDeltaPos.X() = m_pReferenceDevice->GetFontMetric().GetAscent(); |
| aDeltaPos.Y() = (int)((double)m_pReferenceDevice->GetFontMetric().GetDescent() * fXScale); |
| fYScale = fXScale; |
| fTempXScale = 1.0; |
| fSkewA = -fSkewB; |
| fSkewB = 0.0; |
| } |
| else if( ( rGlyphs[i].m_nGlyphId & GF_ROTMASK ) == GF_ROTR ) |
| { |
| fDeltaAngle = -M_PI/2.0; |
| aDeltaPos.X() = (int)((double)m_pReferenceDevice->GetFontMetric().GetDescent()*fXScale); |
| aDeltaPos.Y() = -m_pReferenceDevice->GetFontMetric().GetAscent(); |
| fYScale = fXScale; |
| fTempXScale = 1.0; |
| fSkewA = fSkewB; |
| fSkewB = 0.0; |
| } |
| aDeltaPos += (m_pReferenceDevice->PixelToLogic( Point( (int)((double)nXOffset/fXScale), 0 ) ) - m_pReferenceDevice->PixelToLogic( Point() ) ); |
| if( i < rGlyphs.size()-1 ) |
| { |
| long nOffsetX = rGlyphs[i+1].m_aPos.X() - rGlyphs[i].m_aPos.X(); |
| long nOffsetY = rGlyphs[i+1].m_aPos.Y() - rGlyphs[i].m_aPos.Y(); |
| nXOffset += (int)sqrt(double(nOffsetX*nOffsetX + nOffsetY*nOffsetY)); |
| } |
| if( ! rGlyphs[i].m_nGlyphId ) |
| continue; |
| |
| aDeltaPos = rRotScale.transform( aDeltaPos ); |
| |
| Matrix3 aMat; |
| if( fSkewB != 0.0 || fSkewA != 0.0 ) |
| aMat.skew( fSkewA, fSkewB ); |
| aMat.scale( fTempXScale, fYScale ); |
| aMat.rotate( fAngle+fDeltaAngle ); |
| aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() ); |
| aMat.append( m_aPages.back(), rLine ); |
| rLine.append( " Tm" ); |
| if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId ) |
| { |
| rLine.append( " /F" ); |
| rLine.append( rGlyphs[i].m_nMappedFontId ); |
| rLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( nFontHeight, rLine, true ); |
| rLine.append( " Tf" ); |
| } |
| rLine.append( "<" ); |
| appendHex( rGlyphs[i].m_nMappedGlyphId, rLine ); |
| rLine.append( ">Tj\n" ); |
| } |
| } |
| |
| void PDFWriterImpl::drawHorizontalGlyphs( |
| const std::vector<PDFWriterImpl::PDFGlyph>& rGlyphs, |
| OStringBuffer& rLine, |
| const Point& rAlignOffset, |
| double fAngle, |
| double fXScale, |
| double fSkew, |
| sal_Int32 nFontHeight, |
| sal_Int32 nPixelFontHeight |
| ) |
| { |
| // horizontal (= normal) case |
| |
| // fill in run end indices |
| // end is marked by index of the first glyph of the next run |
| // a run is marked by same mapped font id and same Y position |
| std::vector< sal_uInt32 > aRunEnds; |
| aRunEnds.reserve( rGlyphs.size() ); |
| for( size_t i = 1; i < rGlyphs.size(); i++ ) |
| { |
| if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId || |
| rGlyphs[i].m_aPos.Y() != rGlyphs[i-1].m_aPos.Y() ) |
| { |
| aRunEnds.push_back(i); |
| } |
| } |
| // last run ends at last glyph |
| aRunEnds.push_back( rGlyphs.size() ); |
| |
| // loop over runs of the same font |
| sal_uInt32 nBeginRun = 0; |
| for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ ) |
| { |
| // setup text matrix |
| Point aCurPos = rGlyphs[nBeginRun].m_aPos; |
| // back transformation to current coordinate system |
| aCurPos = m_pReferenceDevice->PixelToLogic( aCurPos ); |
| aCurPos += rAlignOffset; |
| // the first run can be set with "Td" operator |
| // subsequent use of that operator would move |
| // the texline matrix relative to what was set before |
| // making use of that would drive us into rounding issues |
| Matrix3 aMat; |
| if( nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 ) |
| { |
| m_aPages.back().appendPoint( aCurPos, rLine, false ); |
| rLine.append( " Td " ); |
| } |
| else |
| { |
| if( fSkew != 0.0 ) |
| aMat.skew( 0.0, fSkew ); |
| aMat.scale( fXScale, 1.0 ); |
| aMat.rotate( fAngle ); |
| aMat.translate( aCurPos.X(), aCurPos.Y() ); |
| aMat.append( m_aPages.back(), rLine ); |
| rLine.append( " Tm\n" ); |
| } |
| // set up correct font |
| rLine.append( "/F" ); |
| rLine.append( rGlyphs[nBeginRun].m_nMappedFontId ); |
| rLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( nFontHeight, rLine, true ); |
| rLine.append( " Tf" ); |
| |
| // output glyphs using Tj or TJ |
| OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 ); |
| aKernedLine.append( "[<" ); |
| aUnkernedLine.append( '<' ); |
| appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine ); |
| appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine ); |
| |
| aMat.invert(); |
| bool bNeedKern = false; |
| for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ ) |
| { |
| appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine ); |
| // check if default glyph positioning is sufficient |
| const Point aThisPos = aMat.transform( rGlyphs[nPos].m_aPos ); |
| const Point aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos ); |
| double fAdvance = aThisPos.X() - aPrevPos.X(); |
| fAdvance *= 1000.0 / nPixelFontHeight; |
| const sal_Int32 nAdjustment = (sal_Int32)(rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5); |
| if( nAdjustment != 0 ) |
| { |
| // apply individual glyph positioning |
| bNeedKern = true; |
| aKernedLine.append( ">" ); |
| aKernedLine.append( nAdjustment ); |
| aKernedLine.append( "<" ); |
| } |
| appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine ); |
| } |
| aKernedLine.append( ">]TJ\n" ); |
| aUnkernedLine.append( ">Tj\n" ); |
| rLine.append( bNeedKern ? aKernedLine : aUnkernedLine ); |
| |
| // set beginning of next run |
| nBeginRun = aRunEnds[nRun]; |
| } |
| } |
| |
| void PDFWriterImpl::drawLayout( SalLayout& rLayout, const String& rText, bool bTextLines ) |
| { |
| // relief takes precedence over shadow (see outdev3.cxx) |
| if( m_aCurrentPDFState.m_aFont.GetRelief() != RELIEF_NONE ) |
| { |
| drawRelief( rLayout, rText, bTextLines ); |
| return; |
| } |
| else if( m_aCurrentPDFState.m_aFont.IsShadow() ) |
| drawShadow( rLayout, rText, bTextLines ); |
| |
| OStringBuffer aLine( 512 ); |
| |
| const int nMaxGlyphs = 256; |
| |
| sal_GlyphId pGlyphs[nMaxGlyphs]; |
| sal_Int32 pGlyphWidths[nMaxGlyphs]; |
| sal_uInt8 pMappedGlyphs[nMaxGlyphs]; |
| sal_Int32 pMappedFontObjects[nMaxGlyphs]; |
| std::vector<sal_Ucs> aUnicodes; |
| aUnicodes.reserve( nMaxGlyphs ); |
| sal_Int32 pUnicodesPerGlyph[nMaxGlyphs]; |
| int pCharPosAry[nMaxGlyphs]; |
| sal_Int32 nAdvanceWidths[nMaxGlyphs]; |
| const ImplFontData* pFallbackFonts[nMaxGlyphs]; |
| bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical(); |
| int nGlyphs; |
| int nIndex = 0; |
| int nMinCharPos = 0, nMaxCharPos = rText.Len()-1; |
| double fXScale = 1.0; |
| double fSkew = 0.0; |
| sal_Int32 nPixelFontHeight = m_pReferenceDevice->mpFontEntry->maFontSelData.mnHeight; |
| TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlign(); |
| |
| // transform font height back to current units |
| // note: the layout calculates in outdevs device pixel !! |
| sal_Int32 nFontHeight = m_pReferenceDevice->ImplDevicePixelToLogicHeight( nPixelFontHeight ); |
| if( m_aCurrentPDFState.m_aFont.GetWidth() ) |
| { |
| Font aFont( m_aCurrentPDFState.m_aFont ); |
| aFont.SetWidth( 0 ); |
| FontMetric aMetric = m_pReferenceDevice->GetFontMetric( aFont ); |
| if( aMetric.GetWidth() != m_aCurrentPDFState.m_aFont.GetWidth() ) |
| { |
| fXScale = |
| (double)m_aCurrentPDFState.m_aFont.GetWidth() / |
| (double)aMetric.GetWidth(); |
| } |
| // force state before GetFontMetric |
| m_pReferenceDevice->ImplNewFont(); |
| } |
| |
| // perform artificial italics if necessary |
| if( ( m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_NORMAL || |
| m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_OBLIQUE ) && |
| !( m_pReferenceDevice->mpFontEntry->maFontSelData.mpFontData->GetSlant() == ITALIC_NORMAL || |
| m_pReferenceDevice->mpFontEntry->maFontSelData.mpFontData->GetSlant() == ITALIC_OBLIQUE ) |
| ) |
| { |
| fSkew = M_PI/12.0; |
| } |
| |
| // if the mapmode is distorted we need to adjust for that also |
| if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() ) |
| { |
| fXScale *= double(m_aCurrentPDFState.m_aMapMode.GetScaleX()) / double(m_aCurrentPDFState.m_aMapMode.GetScaleY()); |
| } |
| |
| int nAngle = m_aCurrentPDFState.m_aFont.GetOrientation(); |
| // normalize angles |
| while( nAngle < 0 ) |
| nAngle += 3600; |
| nAngle = nAngle % 3600; |
| double fAngle = (double)nAngle * M_PI / 1800.0; |
| |
| Matrix3 aRotScale; |
| aRotScale.scale( fXScale, 1.0 ); |
| if( fAngle != 0.0 ) |
| aRotScale.rotate( -fAngle ); |
| |
| bool bPop = false; |
| bool bABold = false; |
| // artificial bold necessary ? |
| if( m_pReferenceDevice->mpFontEntry->maFontSelData.mpFontData->GetWeight() <= WEIGHT_MEDIUM && |
| m_pReferenceDevice->mpFontEntry->maFontSelData.GetWeight() > WEIGHT_MEDIUM ) |
| { |
| if( ! bPop ) |
| aLine.append( "q " ); |
| bPop = true; |
| bABold = true; |
| } |
| // setup text colors (if necessary) |
| Color aStrokeColor( COL_TRANSPARENT ); |
| Color aNonStrokeColor( COL_TRANSPARENT ); |
| |
| if( m_aCurrentPDFState.m_aFont.IsOutline() ) |
| { |
| aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor(); |
| aNonStrokeColor = Color( COL_WHITE ); |
| } |
| else |
| aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor(); |
| if( bABold ) |
| aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor(); |
| |
| if( aStrokeColor != Color( COL_TRANSPARENT ) && aStrokeColor != m_aCurrentPDFState.m_aLineColor ) |
| { |
| if( ! bPop ) |
| aLine.append( "q " ); |
| bPop = true; |
| appendStrokingColor( aStrokeColor, aLine ); |
| aLine.append( "\n" ); |
| } |
| if( aNonStrokeColor != Color( COL_TRANSPARENT ) && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor ) |
| { |
| if( ! bPop ) |
| aLine.append( "q " ); |
| bPop = true; |
| appendNonStrokingColor( aNonStrokeColor, aLine ); |
| aLine.append( "\n" ); |
| } |
| |
| // begin text object |
| aLine.append( "BT\n" ); |
| // outline attribute ? |
| if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold ) |
| { |
| // set correct text mode, set stroke width |
| aLine.append( "2 Tr " ); // fill, then stroke |
| |
| if( m_aCurrentPDFState.m_aFont.IsOutline() ) |
| { |
| // unclear what to do in case of outline and artificial bold |
| // for the time being outline wins |
| aLine.append( "0.25 w \n" ); |
| } |
| else |
| { |
| double fW = (double)m_aCurrentPDFState.m_aFont.GetHeight() / 30.0; |
| m_aPages.back().appendMappedLength( fW, aLine ); |
| aLine.append ( " w\n" ); |
| } |
| } |
| |
| FontMetric aRefDevFontMetric = m_pReferenceDevice->GetFontMetric(); |
| |
| // collect the glyphs into a single array |
| const int nTmpMaxGlyphs = rLayout.GetOrientation() ? 1 : nMaxGlyphs; // #i97991# temporary workaround for #i87686# |
| std::vector< PDFGlyph > aGlyphs; |
| aGlyphs.reserve( nTmpMaxGlyphs ); |
| // first get all the glyphs and register them; coordinates still in Pixel |
| Point aGNGlyphPos; |
| while( (nGlyphs = rLayout.GetNextGlyphs( nTmpMaxGlyphs, pGlyphs, aGNGlyphPos, nIndex, nAdvanceWidths, pCharPosAry )) != 0 ) |
| { |
| aUnicodes.clear(); |
| for( int i = 0; i < nGlyphs; i++ ) |
| { |
| pFallbackFonts[i] = rLayout.GetFallbackFontData( pGlyphs[i] ); |
| |
| // default case: 1 glyph is one unicode |
| pUnicodesPerGlyph[i] = 1; |
| if( (pGlyphs[i] & GF_ISCHAR) ) |
| { |
| aUnicodes.push_back( static_cast<sal_Ucs>(pGlyphs[i] & GF_IDXMASK) ); |
| } |
| else if( pCharPosAry[i] >= nMinCharPos && pCharPosAry[i] <= nMaxCharPos ) |
| { |
| int nChars = 1; |
| aUnicodes.push_back( rText.GetChar( sal::static_int_cast<xub_StrLen>(pCharPosAry[i]) ) ); |
| pUnicodesPerGlyph[i] = 1; |
| // try to handle ligatures and such |
| if( i < nGlyphs-1 ) |
| { |
| nChars = pCharPosAry[i+1] - pCharPosAry[i]; |
| // #i115618# fix for simple RTL+CTL cases |
| // TODO: sanitize for RTL ligatures, more complex CTL, etc. |
| if( nChars < 0 ) |
| nChars = -nChars; |
| else if( nChars == 0 ) |
| nChars = 1; |
| pUnicodesPerGlyph[i] = nChars; |
| for( int n = 1; n < nChars; n++ ) |
| aUnicodes.push_back( rText.GetChar( sal::static_int_cast<xub_StrLen>(pCharPosAry[i]+n) ) ); |
| } |
| // #i36691# hack that is needed because currently the pGlyphs[] |
| // argument is ignored for embeddable fonts and so the layout |
| // engine's glyph work is ignored (i.e. char mirroring) |
| // TODO: a real solution would be to map the layout engine's |
| // glyphid (i.e. FreeType's synthetic glyphid for a Type1 font) |
| // back to unicode and then to embeddable font's encoding |
| if( getReferenceDevice()->GetLayoutMode() & TEXT_LAYOUT_BIDI_RTL ) |
| { |
| size_t nI = aUnicodes.size()-1; |
| for( int n = 0; n < nChars; n++, nI-- ) |
| aUnicodes[nI] = static_cast<sal_Ucs>(GetMirroredChar(aUnicodes[nI])); |
| } |
| } |
| else |
| aUnicodes.push_back( 0 ); |
| // note: in case of ctl one character may result |
| // in multiple glyphs. The current SalLayout |
| // implementations set -1 then to indicate that no direct |
| // mapping is possible |
| } |
| |
| registerGlyphs( nGlyphs, pGlyphs, pGlyphWidths, &aUnicodes[0], pUnicodesPerGlyph, pMappedGlyphs, pMappedFontObjects, pFallbackFonts ); |
| |
| for( int i = 0; i < nGlyphs; i++ ) |
| { |
| aGlyphs.push_back( PDFGlyph( aGNGlyphPos, |
| pGlyphWidths[i], |
| pGlyphs[i], |
| pMappedFontObjects[i], |
| pMappedGlyphs[i] ) ); |
| if( bVertical ) |
| aGNGlyphPos.Y() += nAdvanceWidths[i]/rLayout.GetUnitsPerPixel(); |
| else |
| aGNGlyphPos.X() += nAdvanceWidths[i]/rLayout.GetUnitsPerPixel(); |
| } |
| } |
| |
| Point aAlignOffset; |
| if ( eAlign == ALIGN_BOTTOM ) |
| aAlignOffset.Y() -= aRefDevFontMetric.GetDescent(); |
| else if ( eAlign == ALIGN_TOP ) |
| aAlignOffset.Y() += aRefDevFontMetric.GetAscent(); |
| if( aAlignOffset.X() || aAlignOffset.Y() ) |
| aAlignOffset = aRotScale.transform( aAlignOffset ); |
| |
| /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original |
| string contained only on of the UTF16 BOMs |
| */ |
| if( ! aGlyphs.empty() ) |
| { |
| if( bVertical ) |
| drawVerticalGlyphs( aGlyphs, aLine, aAlignOffset, aRotScale, fAngle, fXScale, fSkew, nFontHeight ); |
| else |
| drawHorizontalGlyphs( aGlyphs, aLine, aAlignOffset, fAngle, fXScale, fSkew, nFontHeight, nPixelFontHeight ); |
| } |
| |
| // end textobject |
| aLine.append( "ET\n" ); |
| if( bPop ) |
| aLine.append( "Q\n" ); |
| |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| |
| // draw eventual textlines |
| FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout(); |
| FontUnderline eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline(); |
| FontUnderline eOverline = m_aCurrentPDFState.m_aFont.GetOverline(); |
| if( bTextLines && |
| ( |
| ( eUnderline != UNDERLINE_NONE && eUnderline != UNDERLINE_DONTKNOW ) || |
| ( eOverline != UNDERLINE_NONE && eOverline != UNDERLINE_DONTKNOW ) || |
| ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW ) |
| ) |
| ) |
| { |
| sal_Bool bUnderlineAbove = OutputDevice::ImplIsUnderlineAbove( m_aCurrentPDFState.m_aFont ); |
| if( m_aCurrentPDFState.m_aFont.IsWordLineMode() ) |
| { |
| Point aPos, aStartPt; |
| sal_Int32 nWidth = 0, nAdvance=0; |
| for( int nStart = 0;;) |
| { |
| sal_GlyphId aGlyphId; |
| if( !rLayout.GetNextGlyphs( 1, &aGlyphId, aPos, nStart, &nAdvance ) ) |
| break; |
| |
| if( !rLayout.IsSpacingGlyph( aGlyphId ) ) |
| { |
| if( !nWidth ) |
| aStartPt = aPos; |
| |
| nWidth += nAdvance; |
| } |
| else if( nWidth > 0 ) |
| { |
| drawTextLine( m_pReferenceDevice->PixelToLogic( aStartPt ), |
| m_pReferenceDevice->ImplDevicePixelToLogicWidth( nWidth ), |
| eStrikeout, eUnderline, eOverline, bUnderlineAbove ); |
| nWidth = 0; |
| } |
| } |
| |
| if( nWidth > 0 ) |
| { |
| drawTextLine( m_pReferenceDevice->PixelToLogic( aStartPt ), |
| m_pReferenceDevice->ImplDevicePixelToLogicWidth( nWidth ), |
| eStrikeout, eUnderline, eOverline, bUnderlineAbove ); |
| } |
| } |
| else |
| { |
| Point aStartPt = rLayout.GetDrawPosition(); |
| int nWidth = rLayout.GetTextWidth() / rLayout.GetUnitsPerPixel(); |
| drawTextLine( m_pReferenceDevice->PixelToLogic( aStartPt ), |
| m_pReferenceDevice->ImplDevicePixelToLogicWidth( nWidth ), |
| eStrikeout, eUnderline, eOverline, bUnderlineAbove ); |
| } |
| } |
| |
| // write eventual emphasis marks |
| if( m_aCurrentPDFState.m_aFont.GetEmphasisMark() & EMPHASISMARK_STYLE ) |
| { |
| PolyPolygon aEmphPoly; |
| Rectangle aEmphRect1; |
| Rectangle aEmphRect2; |
| long nEmphYOff; |
| long nEmphWidth; |
| long nEmphHeight; |
| sal_Bool bEmphPolyLine; |
| FontEmphasisMark nEmphMark; |
| |
| push( PUSH_ALL ); |
| |
| aLine.setLength( 0 ); |
| aLine.append( "q\n" ); |
| |
| nEmphMark = m_pReferenceDevice->ImplGetEmphasisMarkStyle( m_aCurrentPDFState.m_aFont ); |
| if ( nEmphMark & EMPHASISMARK_POS_BELOW ) |
| nEmphHeight = m_pReferenceDevice->mnEmphasisDescent; |
| else |
| nEmphHeight = m_pReferenceDevice->mnEmphasisAscent; |
| m_pReferenceDevice->ImplGetEmphasisMark( aEmphPoly, |
| bEmphPolyLine, |
| aEmphRect1, |
| aEmphRect2, |
| nEmphYOff, |
| nEmphWidth, |
| nEmphMark, |
| m_pReferenceDevice->ImplDevicePixelToLogicWidth(nEmphHeight), |
| m_pReferenceDevice->mpFontEntry->mnOrientation ); |
| if ( bEmphPolyLine ) |
| { |
| setLineColor( m_aCurrentPDFState.m_aFont.GetColor() ); |
| setFillColor( Color( COL_TRANSPARENT ) ); |
| } |
| else |
| { |
| setFillColor( m_aCurrentPDFState.m_aFont.GetColor() ); |
| setLineColor( Color( COL_TRANSPARENT ) ); |
| } |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| |
| Point aOffset = Point(0,0); |
| |
| if ( nEmphMark & EMPHASISMARK_POS_BELOW ) |
| aOffset.Y() += m_pReferenceDevice->mpFontEntry->maMetric.mnDescent + nEmphYOff; |
| else |
| aOffset.Y() -= m_pReferenceDevice->mpFontEntry->maMetric.mnAscent + nEmphYOff; |
| |
| long nEmphWidth2 = nEmphWidth / 2; |
| long nEmphHeight2 = nEmphHeight / 2; |
| aOffset += Point( nEmphWidth2, nEmphHeight2 ); |
| |
| if ( eAlign == ALIGN_BOTTOM ) |
| aOffset.Y() -= m_pReferenceDevice->mpFontEntry->maMetric.mnDescent; |
| else if ( eAlign == ALIGN_TOP ) |
| aOffset.Y() += m_pReferenceDevice->mpFontEntry->maMetric.mnAscent; |
| |
| for( int nStart = 0;;) |
| { |
| Point aPos; |
| sal_GlyphId aGlyphId; |
| sal_Int32 nAdvance; |
| if( !rLayout.GetNextGlyphs( 1, &aGlyphId, aPos, nStart, &nAdvance ) ) |
| break; |
| |
| if( !rLayout.IsSpacingGlyph( aGlyphId ) ) |
| { |
| Point aAdjOffset = aOffset; |
| aAdjOffset.X() += (nAdvance - nEmphWidth) / 2; |
| aAdjOffset = aRotScale.transform( aAdjOffset ); |
| |
| aAdjOffset -= Point( nEmphWidth2, nEmphHeight2 ); |
| |
| aPos += aAdjOffset; |
| aPos = m_pReferenceDevice->PixelToLogic( aPos ); |
| drawEmphasisMark( aPos.X(), aPos.Y(), |
| aEmphPoly, bEmphPolyLine, |
| aEmphRect1, aEmphRect2 ); |
| } |
| } |
| |
| writeBuffer( "Q\n", 2 ); |
| pop(); |
| } |
| |
| } |
| |
| void PDFWriterImpl::drawEmphasisMark( long nX, long nY, |
| const PolyPolygon& rPolyPoly, sal_Bool bPolyLine, |
| const Rectangle& rRect1, const Rectangle& rRect2 ) |
| { |
| // TODO: pass nWidth as width of this mark |
| // long nWidth = 0; |
| |
| if ( rPolyPoly.Count() ) |
| { |
| if ( bPolyLine ) |
| { |
| Polygon aPoly = rPolyPoly.GetObject( 0 ); |
| aPoly.Move( nX, nY ); |
| drawPolyLine( aPoly ); |
| } |
| else |
| { |
| PolyPolygon aPolyPoly = rPolyPoly; |
| aPolyPoly.Move( nX, nY ); |
| drawPolyPolygon( aPolyPoly ); |
| } |
| } |
| |
| if ( !rRect1.IsEmpty() ) |
| { |
| Rectangle aRect( Point( nX+rRect1.Left(), |
| nY+rRect1.Top() ), rRect1.GetSize() ); |
| drawRectangle( aRect ); |
| } |
| |
| if ( !rRect2.IsEmpty() ) |
| { |
| Rectangle aRect( Point( nX+rRect2.Left(), |
| nY+rRect2.Top() ), rRect2.GetSize() ); |
| |
| drawRectangle( aRect ); |
| } |
| } |
| |
| void PDFWriterImpl::drawText( const Point& rPos, const String& rText, xub_StrLen nIndex, xub_StrLen nLen, bool bTextLines ) |
| { |
| MARK( "drawText" ); |
| |
| updateGraphicsState(); |
| |
| // get a layout from the OuputDevice's SalGraphics |
| // this also enforces font substitution and sets the font on SalGraphics |
| SalLayout* pLayout = m_pReferenceDevice->ImplLayout( rText, nIndex, nLen, rPos ); |
| if( pLayout ) |
| { |
| drawLayout( *pLayout, rText, bTextLines ); |
| pLayout->Release(); |
| } |
| } |
| |
| void PDFWriterImpl::drawTextArray( const Point& rPos, const String& rText, const sal_Int32* pDXArray, xub_StrLen nIndex, xub_StrLen nLen, bool bTextLines ) |
| { |
| MARK( "drawText with array" ); |
| |
| updateGraphicsState(); |
| |
| // get a layout from the OuputDevice's SalGraphics |
| // this also enforces font substitution and sets the font on SalGraphics |
| SalLayout* pLayout = m_pReferenceDevice->ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray ); |
| if( pLayout ) |
| { |
| drawLayout( *pLayout, rText, bTextLines ); |
| pLayout->Release(); |
| } |
| } |
| |
| void PDFWriterImpl::drawStretchText( const Point& rPos, sal_uLong nWidth, const String& rText, xub_StrLen nIndex, xub_StrLen nLen, bool bTextLines ) |
| { |
| MARK( "drawStretchText" ); |
| |
| updateGraphicsState(); |
| |
| // get a layout from the OuputDevice's SalGraphics |
| // this also enforces font substitution and sets the font on SalGraphics |
| SalLayout* pLayout = m_pReferenceDevice->ImplLayout( rText, nIndex, nLen, rPos, nWidth ); |
| if( pLayout ) |
| { |
| drawLayout( *pLayout, rText, bTextLines ); |
| pLayout->Release(); |
| } |
| } |
| |
| void PDFWriterImpl::drawText( const Rectangle& rRect, const String& rOrigStr, sal_uInt16 nStyle, bool bTextLines ) |
| { |
| long nWidth = rRect.GetWidth(); |
| long nHeight = rRect.GetHeight(); |
| |
| if ( nWidth <= 0 || nHeight <= 0 ) |
| return; |
| |
| MARK( "drawText with rectangle" ); |
| |
| updateGraphicsState(); |
| |
| // clip with rectangle |
| OStringBuffer aLine; |
| aLine.append( "q " ); |
| m_aPages.back().appendRect( rRect, aLine ); |
| aLine.append( " W* n\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| |
| // if disabled text is needed, put in here |
| |
| Point aPos = rRect.TopLeft(); |
| |
| long nTextHeight = m_pReferenceDevice->GetTextHeight(); |
| xub_StrLen nMnemonicPos = STRING_NOTFOUND; |
| |
| String aStr = rOrigStr; |
| if ( nStyle & TEXT_DRAW_MNEMONIC ) |
| aStr = m_pReferenceDevice->GetNonMnemonicString( aStr, nMnemonicPos ); |
| |
| // multiline text |
| if ( nStyle & TEXT_DRAW_MULTILINE ) |
| { |
| XubString aLastLine; |
| ImplMultiTextLineInfo aMultiLineInfo; |
| ImplTextLineInfo* pLineInfo; |
| long nMaxTextWidth; |
| xub_StrLen i; |
| xub_StrLen nLines; |
| xub_StrLen nFormatLines; |
| |
| if ( nTextHeight ) |
| { |
| ::vcl::DefaultTextLayout aLayout( *m_pReferenceDevice ); |
| nMaxTextWidth = OutputDevice::ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, aLayout ); |
| nLines = (xub_StrLen)(nHeight/nTextHeight); |
| nFormatLines = aMultiLineInfo.Count(); |
| if ( !nLines ) |
| nLines = 1; |
| if ( nFormatLines > nLines ) |
| { |
| if ( nStyle & TEXT_DRAW_ENDELLIPSIS ) |
| { |
| // handle last line |
| nFormatLines = nLines-1; |
| |
| pLineInfo = aMultiLineInfo.GetLine( nFormatLines ); |
| aLastLine = aStr.Copy( pLineInfo->GetIndex() ); |
| aLastLine.ConvertLineEnd( LINEEND_LF ); |
| // replace line feed by space |
| xub_StrLen nLastLineLen = aLastLine.Len(); |
| for ( i = 0; i < nLastLineLen; i++ ) |
| { |
| if ( aLastLine.GetChar( i ) == _LF ) |
| aLastLine.SetChar( i, ' ' ); |
| } |
| aLastLine = m_pReferenceDevice->GetEllipsisString( aLastLine, nWidth, nStyle ); |
| nStyle &= ~(TEXT_DRAW_VCENTER | TEXT_DRAW_BOTTOM); |
| nStyle |= TEXT_DRAW_TOP; |
| } |
| } |
| |
| // vertical alignment |
| if ( nStyle & TEXT_DRAW_BOTTOM ) |
| aPos.Y() += nHeight-(nFormatLines*nTextHeight); |
| else if ( nStyle & TEXT_DRAW_VCENTER ) |
| aPos.Y() += (nHeight-(nFormatLines*nTextHeight))/2; |
| |
| // draw all lines excluding the last |
| for ( i = 0; i < nFormatLines; i++ ) |
| { |
| pLineInfo = aMultiLineInfo.GetLine( i ); |
| if ( nStyle & TEXT_DRAW_RIGHT ) |
| aPos.X() += nWidth-pLineInfo->GetWidth(); |
| else if ( nStyle & TEXT_DRAW_CENTER ) |
| aPos.X() += (nWidth-pLineInfo->GetWidth())/2; |
| xub_StrLen nIndex = pLineInfo->GetIndex(); |
| xub_StrLen nLineLen = pLineInfo->GetLen(); |
| drawText( aPos, aStr, nIndex, nLineLen, bTextLines ); |
| // mnemonics should not appear in documents, |
| // if the need arises, put them in here |
| aPos.Y() += nTextHeight; |
| aPos.X() = rRect.Left(); |
| } |
| |
| |
| // output last line left adjusted since it was shortened |
| if ( aLastLine.Len() ) |
| drawText( aPos, aLastLine, 0, STRING_LEN, bTextLines ); |
| } |
| } |
| else |
| { |
| long nTextWidth = m_pReferenceDevice->GetTextWidth( aStr ); |
| |
| // Evt. Text kuerzen |
| if ( nTextWidth > nWidth ) |
| { |
| if ( nStyle & (TEXT_DRAW_ENDELLIPSIS | TEXT_DRAW_PATHELLIPSIS | TEXT_DRAW_NEWSELLIPSIS) ) |
| { |
| aStr = m_pReferenceDevice->GetEllipsisString( aStr, nWidth, nStyle ); |
| nStyle &= ~(TEXT_DRAW_CENTER | TEXT_DRAW_RIGHT); |
| nStyle |= TEXT_DRAW_LEFT; |
| nTextWidth = m_pReferenceDevice->GetTextWidth( aStr ); |
| } |
| } |
| |
| // vertical alignment |
| if ( nStyle & TEXT_DRAW_RIGHT ) |
| aPos.X() += nWidth-nTextWidth; |
| else if ( nStyle & TEXT_DRAW_CENTER ) |
| aPos.X() += (nWidth-nTextWidth)/2; |
| |
| if ( nStyle & TEXT_DRAW_BOTTOM ) |
| aPos.Y() += nHeight-nTextHeight; |
| else if ( nStyle & TEXT_DRAW_VCENTER ) |
| aPos.Y() += (nHeight-nTextHeight)/2; |
| |
| // mnemonics should be inserted here if the need arises |
| |
| // draw the actual text |
| drawText( aPos, aStr, 0, STRING_LEN, bTextLines ); |
| } |
| |
| // reset clip region to original value |
| aLine.setLength( 0 ); |
| aLine.append( "Q\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop ) |
| { |
| MARK( "drawLine" ); |
| |
| updateGraphicsState(); |
| |
| if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) ) |
| return; |
| |
| OStringBuffer aLine; |
| m_aPages.back().appendPoint( rStart, aLine ); |
| aLine.append( " m " ); |
| m_aPages.back().appendPoint( rStop, aLine ); |
| aLine.append( " l S\n" ); |
| |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo ) |
| { |
| MARK( "drawLine with LineInfo" ); |
| updateGraphicsState(); |
| |
| if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) ) |
| return; |
| |
| if( rInfo.GetStyle() == LINE_SOLID && rInfo.GetWidth() < 2 ) |
| { |
| drawLine( rStart, rStop ); |
| return; |
| } |
| |
| OStringBuffer aLine; |
| |
| aLine.append( "q " ); |
| if( m_aPages.back().appendLineInfo( rInfo, aLine ) ) |
| { |
| m_aPages.back().appendPoint( rStart, aLine ); |
| aLine.append( " m " ); |
| m_aPages.back().appendPoint( rStop, aLine ); |
| aLine.append( " l S Q\n" ); |
| |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| else |
| { |
| PDFWriter::ExtLineInfo aInfo; |
| convertLineInfoToExtLineInfo( rInfo, aInfo ); |
| Point aPolyPoints[2] = { rStart, rStop }; |
| Polygon aPoly( 2, aPolyPoints ); |
| drawPolyLine( aPoly, aInfo ); |
| } |
| } |
| |
| void PDFWriterImpl::drawWaveLine( const Point& rStart, const Point& rStop, sal_Int32 nDelta, sal_Int32 nLineWidth ) |
| { |
| Point aDiff( rStop-rStart ); |
| double fLen = sqrt( (double)(aDiff.X()*aDiff.X() + aDiff.Y()*aDiff.Y()) ); |
| if( fLen < 1.0 ) |
| return; |
| |
| MARK( "drawWaveLine" ); |
| updateGraphicsState(); |
| |
| if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) ) |
| return; |
| |
| OStringBuffer aLine( 512 ); |
| aLine.append( "q " ); |
| m_aPages.back().appendMappedLength( nLineWidth, aLine, true ); |
| aLine.append( " w " ); |
| |
| appendDouble( (double)aDiff.X()/fLen, aLine ); |
| aLine.append( ' ' ); |
| appendDouble( -(double)aDiff.Y()/fLen, aLine ); |
| aLine.append( ' ' ); |
| appendDouble( (double)aDiff.Y()/fLen, aLine ); |
| aLine.append( ' ' ); |
| appendDouble( (double)aDiff.X()/fLen, aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( rStart, aLine ); |
| aLine.append( " cm " ); |
| m_aPages.back().appendWaveLine( (sal_Int32)fLen, 0, nDelta, aLine ); |
| aLine.append( "Q\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| #define WCONV( x ) m_pReferenceDevice->ImplDevicePixelToLogicWidth( x ) |
| #define HCONV( x ) m_pReferenceDevice->ImplDevicePixelToLogicHeight( x ) |
| |
| void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, long nWidth, FontUnderline eTextLine, Color aColor, bool bIsAbove ) |
| { |
| // note: units in pFontEntry are ref device pixel |
| ImplFontEntry* pFontEntry = m_pReferenceDevice->mpFontEntry; |
| long nLineHeight = 0; |
| long nLinePos = 0; |
| |
| appendStrokingColor( aColor, aLine ); |
| aLine.append( "\n" ); |
| |
| if ( bIsAbove ) |
| { |
| if ( !pFontEntry->maMetric.mnAboveWUnderlineSize ) |
| m_pReferenceDevice->ImplInitAboveTextLineSize(); |
| nLineHeight = HCONV( pFontEntry->maMetric.mnAboveWUnderlineSize ); |
| nLinePos = HCONV( pFontEntry->maMetric.mnAboveWUnderlineOffset ); |
| } |
| else |
| { |
| if ( !pFontEntry->maMetric.mnWUnderlineSize ) |
| m_pReferenceDevice->ImplInitTextLineSize(); |
| nLineHeight = HCONV( pFontEntry->maMetric.mnWUnderlineSize ); |
| nLinePos = HCONV( pFontEntry->maMetric.mnWUnderlineOffset ); |
| } |
| if ( (eTextLine == UNDERLINE_SMALLWAVE) && (nLineHeight > 3) ) |
| nLineHeight = 3; |
| |
| long nLineWidth = getReferenceDevice()->mnDPIX/450; |
| if ( ! nLineWidth ) |
| nLineWidth = 1; |
| |
| if ( eTextLine == UNDERLINE_BOLDWAVE ) |
| nLineWidth = 3*nLineWidth; |
| |
| m_aPages.back().appendMappedLength( (sal_Int32)nLineWidth, aLine ); |
| aLine.append( " w " ); |
| |
| if ( eTextLine == UNDERLINE_DOUBLEWAVE ) |
| { |
| long nOrgLineHeight = nLineHeight; |
| nLineHeight /= 3; |
| if ( nLineHeight < 2 ) |
| { |
| if ( nOrgLineHeight > 1 ) |
| nLineHeight = 2; |
| else |
| nLineHeight = 1; |
| } |
| long nLineDY = nOrgLineHeight-(nLineHeight*2); |
| if ( nLineDY < nLineWidth ) |
| nLineDY = nLineWidth; |
| long nLineDY2 = nLineDY/2; |
| if ( !nLineDY2 ) |
| nLineDY2 = 1; |
| |
| nLinePos -= nLineWidth-nLineDY2; |
| |
| m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine ); |
| |
| nLinePos += nLineWidth+nLineDY; |
| m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine ); |
| } |
| else |
| { |
| if ( eTextLine != UNDERLINE_BOLDWAVE ) |
| nLinePos -= nLineWidth/2; |
| m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine ); |
| } |
| } |
| |
| void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, long nWidth, FontUnderline eTextLine, Color aColor, bool bIsAbove ) |
| { |
| // note: units in pFontEntry are ref device pixel |
| ImplFontEntry* pFontEntry = m_pReferenceDevice->mpFontEntry; |
| long nLineHeight = 0; |
| long nLinePos = 0; |
| long nLinePos2 = 0; |
| |
| if ( eTextLine > UNDERLINE_BOLDWAVE ) |
| eTextLine = UNDERLINE_SINGLE; |
| |
| switch ( eTextLine ) |
| { |
| case UNDERLINE_SINGLE: |
| case UNDERLINE_DOTTED: |
| case UNDERLINE_DASH: |
| case UNDERLINE_LONGDASH: |
| case UNDERLINE_DASHDOT: |
| case UNDERLINE_DASHDOTDOT: |
| if ( bIsAbove ) |
| { |
| if ( !pFontEntry->maMetric.mnAboveUnderlineSize ) |
| m_pReferenceDevice->ImplInitAboveTextLineSize(); |
| nLineHeight = HCONV( pFontEntry->maMetric.mnAboveUnderlineSize ); |
| nLinePos = HCONV( pFontEntry->maMetric.mnAboveUnderlineOffset ); |
| } |
| else |
| { |
| if ( !pFontEntry->maMetric.mnUnderlineSize ) |
| m_pReferenceDevice->ImplInitTextLineSize(); |
| nLineHeight = HCONV( pFontEntry->maMetric.mnUnderlineSize ); |
| nLinePos = HCONV( pFontEntry->maMetric.mnUnderlineOffset ); |
| } |
| break; |
| case UNDERLINE_BOLD: |
| case UNDERLINE_BOLDDOTTED: |
| case UNDERLINE_BOLDDASH: |
| case UNDERLINE_BOLDLONGDASH: |
| case UNDERLINE_BOLDDASHDOT: |
| case UNDERLINE_BOLDDASHDOTDOT: |
| if ( bIsAbove ) |
| { |
| if ( !pFontEntry->maMetric.mnAboveBUnderlineSize ) |
| m_pReferenceDevice->ImplInitAboveTextLineSize(); |
| nLineHeight = HCONV( pFontEntry->maMetric.mnAboveBUnderlineSize ); |
| nLinePos = HCONV( pFontEntry->maMetric.mnAboveBUnderlineOffset ); |
| } |
| else |
| { |
| if ( !pFontEntry->maMetric.mnBUnderlineSize ) |
| m_pReferenceDevice->ImplInitTextLineSize(); |
| nLineHeight = HCONV( pFontEntry->maMetric.mnBUnderlineSize ); |
| nLinePos = HCONV( pFontEntry->maMetric.mnBUnderlineOffset ); |
| nLinePos += nLineHeight/2; |
| } |
| break; |
| case UNDERLINE_DOUBLE: |
| if ( bIsAbove ) |
| { |
| if ( !pFontEntry->maMetric.mnAboveDUnderlineSize ) |
| m_pReferenceDevice->ImplInitAboveTextLineSize(); |
| nLineHeight = HCONV( pFontEntry->maMetric.mnAboveDUnderlineSize ); |
| nLinePos = HCONV( pFontEntry->maMetric.mnAboveDUnderlineOffset1 ); |
| nLinePos2 = HCONV( pFontEntry->maMetric.mnAboveDUnderlineOffset2 ); |
| } |
| else |
| { |
| if ( !pFontEntry->maMetric.mnDUnderlineSize ) |
| m_pReferenceDevice->ImplInitTextLineSize(); |
| nLineHeight = HCONV( pFontEntry->maMetric.mnDUnderlineSize ); |
| nLinePos = HCONV( pFontEntry->maMetric.mnDUnderlineOffset1 ); |
| nLinePos2 = HCONV( pFontEntry->maMetric.mnDUnderlineOffset2 ); |
| } |
| default: |
| break; |
| } |
| |
| if ( nLineHeight ) |
| { |
| m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, true ); |
| aLine.append( " w " ); |
| appendStrokingColor( aColor, aLine ); |
| aLine.append( "\n" ); |
| |
| switch ( eTextLine ) |
| { |
| case UNDERLINE_DOTTED: |
| case UNDERLINE_BOLDDOTTED: |
| aLine.append( "[ " ); |
| m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, false ); |
| aLine.append( " ] 0 d\n" ); |
| break; |
| case UNDERLINE_DASH: |
| case UNDERLINE_LONGDASH: |
| case UNDERLINE_BOLDDASH: |
| case UNDERLINE_BOLDLONGDASH: |
| { |
| sal_Int32 nDashLength = 4*nLineHeight; |
| sal_Int32 nVoidLength = 2*nLineHeight; |
| if ( ( eTextLine == UNDERLINE_LONGDASH ) || ( eTextLine == UNDERLINE_BOLDLONGDASH ) ) |
| nDashLength = 8*nLineHeight; |
| |
| aLine.append( "[ " ); |
| m_aPages.back().appendMappedLength( nDashLength, aLine, false ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); |
| aLine.append( " ] 0 d\n" ); |
| } |
| break; |
| case UNDERLINE_DASHDOT: |
| case UNDERLINE_BOLDDASHDOT: |
| { |
| sal_Int32 nDashLength = 4*nLineHeight; |
| sal_Int32 nVoidLength = 2*nLineHeight; |
| aLine.append( "[ " ); |
| m_aPages.back().appendMappedLength( nDashLength, aLine, false ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, false ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); |
| aLine.append( " ] 0 d\n" ); |
| } |
| break; |
| case UNDERLINE_DASHDOTDOT: |
| case UNDERLINE_BOLDDASHDOTDOT: |
| { |
| sal_Int32 nDashLength = 4*nLineHeight; |
| sal_Int32 nVoidLength = 2*nLineHeight; |
| aLine.append( "[ " ); |
| m_aPages.back().appendMappedLength( nDashLength, aLine, false ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, false ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, false ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); |
| aLine.append( " ] 0 d\n" ); |
| } |
| break; |
| default: |
| break; |
| } |
| |
| aLine.append( "0 " ); |
| m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos), aLine, true ); |
| aLine.append( " m " ); |
| m_aPages.back().appendMappedLength( (sal_Int32)nWidth, aLine, false ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos), aLine, true ); |
| aLine.append( " l S\n" ); |
| if ( eTextLine == UNDERLINE_DOUBLE ) |
| { |
| aLine.append( "0 " ); |
| m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos2-nLineHeight), aLine, true ); |
| aLine.append( " m " ); |
| m_aPages.back().appendMappedLength( (sal_Int32)nWidth, aLine, false ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos2-nLineHeight), aLine, true ); |
| aLine.append( " l S\n" ); |
| } |
| } |
| } |
| |
| void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, long nWidth, FontStrikeout eStrikeout, Color aColor ) |
| { |
| // note: units in pFontEntry are ref device pixel |
| ImplFontEntry* pFontEntry = m_pReferenceDevice->mpFontEntry; |
| long nLineHeight = 0; |
| long nLinePos = 0; |
| long nLinePos2 = 0; |
| |
| if ( eStrikeout > STRIKEOUT_X ) |
| eStrikeout = STRIKEOUT_SINGLE; |
| |
| switch ( eStrikeout ) |
| { |
| case STRIKEOUT_SINGLE: |
| if ( !pFontEntry->maMetric.mnStrikeoutSize ) |
| m_pReferenceDevice->ImplInitTextLineSize(); |
| nLineHeight = HCONV( pFontEntry->maMetric.mnStrikeoutSize ); |
| nLinePos = HCONV( pFontEntry->maMetric.mnStrikeoutOffset ); |
| break; |
| case STRIKEOUT_BOLD: |
| if ( !pFontEntry->maMetric.mnBStrikeoutSize ) |
| m_pReferenceDevice->ImplInitTextLineSize(); |
| nLineHeight = HCONV( pFontEntry->maMetric.mnBStrikeoutSize ); |
| nLinePos = HCONV( pFontEntry->maMetric.mnBStrikeoutOffset ); |
| break; |
| case STRIKEOUT_DOUBLE: |
| if ( !pFontEntry->maMetric.mnDStrikeoutSize ) |
| m_pReferenceDevice->ImplInitTextLineSize(); |
| nLineHeight = HCONV( pFontEntry->maMetric.mnDStrikeoutSize ); |
| nLinePos = HCONV( pFontEntry->maMetric.mnDStrikeoutOffset1 ); |
| nLinePos2 = HCONV( pFontEntry->maMetric.mnDStrikeoutOffset2 ); |
| break; |
| default: |
| break; |
| } |
| |
| if ( nLineHeight ) |
| { |
| m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, true ); |
| aLine.append( " w " ); |
| appendStrokingColor( aColor, aLine ); |
| aLine.append( "\n" ); |
| |
| aLine.append( "0 " ); |
| m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos), aLine, true ); |
| aLine.append( " m " ); |
| m_aPages.back().appendMappedLength( (sal_Int32)nWidth, aLine, true ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos), aLine, true ); |
| aLine.append( " l S\n" ); |
| |
| if ( eStrikeout == STRIKEOUT_DOUBLE ) |
| { |
| aLine.append( "0 " ); |
| m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos2-nLineHeight), aLine, true ); |
| aLine.append( " m " ); |
| m_aPages.back().appendMappedLength( (sal_Int32)nWidth, aLine, true ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos2-nLineHeight), aLine, true ); |
| aLine.append( " l S\n" ); |
| } |
| } |
| } |
| |
| void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, long nWidth, FontStrikeout eStrikeout ) |
| { |
| String aStrikeoutChar = String::CreateFromAscii( eStrikeout == STRIKEOUT_SLASH ? "/" : "X" ); |
| String aStrikeout = aStrikeoutChar; |
| while( m_pReferenceDevice->GetTextWidth( aStrikeout ) < nWidth ) |
| aStrikeout.Append( aStrikeout ); |
| |
| // do not get broader than nWidth modulo 1 character |
| while( m_pReferenceDevice->GetTextWidth( aStrikeout ) >= nWidth ) |
| aStrikeout.Erase( 0, 1 ); |
| aStrikeout.Append( aStrikeoutChar ); |
| sal_Bool bShadow = m_aCurrentPDFState.m_aFont.IsShadow(); |
| if ( bShadow ) |
| { |
| Font aFont = m_aCurrentPDFState.m_aFont; |
| aFont.SetShadow( sal_False ); |
| setFont( aFont ); |
| updateGraphicsState(); |
| } |
| |
| // strikeout string is left aligned non-CTL text |
| sal_uLong nOrigTLM = m_pReferenceDevice->GetLayoutMode(); |
| m_pReferenceDevice->SetLayoutMode( TEXT_LAYOUT_BIDI_STRONG|TEXT_LAYOUT_COMPLEX_DISABLED ); |
| drawText( rPos, aStrikeout, 0, aStrikeout.Len(), false ); |
| m_pReferenceDevice->SetLayoutMode( nOrigTLM ); |
| |
| if ( bShadow ) |
| { |
| Font aFont = m_aCurrentPDFState.m_aFont; |
| aFont.SetShadow( sal_True ); |
| setFont( aFont ); |
| updateGraphicsState(); |
| } |
| } |
| |
| void PDFWriterImpl::drawTextLine( const Point& rPos, long nWidth, FontStrikeout eStrikeout, FontUnderline eUnderline, FontUnderline eOverline, bool bUnderlineAbove ) |
| { |
| if ( !nWidth || |
| ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) && |
| ((eUnderline == UNDERLINE_NONE)||(eUnderline == UNDERLINE_DONTKNOW)) && |
| ((eOverline == UNDERLINE_NONE)||(eOverline == UNDERLINE_DONTKNOW)) ) ) |
| return; |
| |
| MARK( "drawTextLine" ); |
| updateGraphicsState(); |
| |
| // note: units in pFontEntry are ref device pixel |
| ImplFontEntry* pFontEntry = m_pReferenceDevice->mpFontEntry; |
| Color aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor; |
| Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor; |
| Color aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor(); |
| bool bStrikeoutDone = false; |
| bool bUnderlineDone = false; |
| bool bOverlineDone = false; |
| |
| if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) ) |
| { |
| drawStrikeoutChar( rPos, nWidth, eStrikeout ); |
| bStrikeoutDone = true; |
| } |
| |
| Point aPos( rPos ); |
| TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlign(); |
| if( eAlign == ALIGN_TOP ) |
| aPos.Y() += HCONV( pFontEntry->maMetric.mnAscent ); |
| else if( eAlign == ALIGN_BOTTOM ) |
| aPos.Y() -= HCONV( pFontEntry->maMetric.mnDescent ); |
| |
| OStringBuffer aLine( 512 ); |
| // save GS |
| aLine.append( "q " ); |
| |
| // rotate and translate matrix |
| double fAngle = (double)m_aCurrentPDFState.m_aFont.GetOrientation() * M_PI / 1800.0; |
| Matrix3 aMat; |
| aMat.rotate( fAngle ); |
| aMat.translate( aPos.X(), aPos.Y() ); |
| aMat.append( m_aPages.back(), aLine ); |
| aLine.append( " cm\n" ); |
| |
| if ( aUnderlineColor.GetTransparency() != 0 ) |
| aUnderlineColor = aStrikeoutColor; |
| |
| if ( (eUnderline == UNDERLINE_SMALLWAVE) || |
| (eUnderline == UNDERLINE_WAVE) || |
| (eUnderline == UNDERLINE_DOUBLEWAVE) || |
| (eUnderline == UNDERLINE_BOLDWAVE) ) |
| { |
| drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); |
| bUnderlineDone = true; |
| } |
| |
| if ( (eOverline == UNDERLINE_SMALLWAVE) || |
| (eOverline == UNDERLINE_WAVE) || |
| (eOverline == UNDERLINE_DOUBLEWAVE) || |
| (eOverline == UNDERLINE_BOLDWAVE) ) |
| { |
| drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true ); |
| bOverlineDone = true; |
| } |
| |
| if ( !bUnderlineDone ) |
| { |
| drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); |
| } |
| |
| if ( !bOverlineDone ) |
| { |
| drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true ); |
| } |
| |
| if ( !bStrikeoutDone ) |
| { |
| drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor ); |
| } |
| |
| aLine.append( "Q\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| void PDFWriterImpl::drawPolygon( const Polygon& rPoly ) |
| { |
| MARK( "drawPolygon" ); |
| |
| updateGraphicsState(); |
| |
| if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && |
| m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) ) |
| return; |
| |
| int nPoints = rPoly.GetSize(); |
| OStringBuffer aLine( 20 * nPoints ); |
| m_aPages.back().appendPolygon( rPoly, aLine ); |
| if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) && |
| m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) ) |
| aLine.append( "B*\n" ); |
| else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) |
| aLine.append( "S\n" ); |
| else |
| aLine.append( "f*\n" ); |
| |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| void PDFWriterImpl::drawPolyPolygon( const PolyPolygon& rPolyPoly ) |
| { |
| MARK( "drawPolyPolygon" ); |
| |
| updateGraphicsState(); |
| |
| if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && |
| m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) ) |
| return; |
| |
| int nPolygons = rPolyPoly.Count(); |
| |
| OStringBuffer aLine( 40 * nPolygons ); |
| m_aPages.back().appendPolyPolygon( rPolyPoly, aLine ); |
| if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) && |
| m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) ) |
| aLine.append( "B*\n" ); |
| else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) |
| aLine.append( "S\n" ); |
| else |
| aLine.append( "f*\n" ); |
| |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| void PDFWriterImpl::drawTransparent( const PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent ) |
| { |
| DBG_ASSERT( nTransparentPercent <= 100, "invalid alpha value" ); |
| nTransparentPercent = nTransparentPercent % 100; |
| |
| MARK( "drawTransparent" ); |
| |
| updateGraphicsState(); |
| |
| if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && |
| m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) ) |
| return; |
| |
| if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDF_1_4 ) |
| { |
| m_aErrors.insert( m_bIsPDF_A1 ? |
| PDFWriter::Warning_Transparency_Omitted_PDFA : |
| PDFWriter::Warning_Transparency_Omitted_PDF13 ); |
| |
| drawPolyPolygon( rPolyPoly ); |
| return; |
| } |
| |
| // create XObject |
| m_aTransparentObjects.push_back( TransparencyEmit() ); |
| // FIXME: polygons with beziers may yield incorrect bound rect |
| m_aTransparentObjects.back().m_aBoundRect = rPolyPoly.GetBoundRect(); |
| // convert rectangle to default user space |
| m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect ); |
| m_aTransparentObjects.back().m_nObject = createObject(); |
| m_aTransparentObjects.back().m_nExtGStateObject = createObject(); |
| m_aTransparentObjects.back().m_fAlpha = (double)(100-nTransparentPercent) / 100.0; |
| m_aTransparentObjects.back().m_pContentStream = new SvMemoryStream( 256, 256 ); |
| // create XObject's content stream |
| OStringBuffer aContent( 256 ); |
| m_aPages.back().appendPolyPolygon( rPolyPoly, aContent ); |
| if( m_aCurrentPDFState.m_aLineColor != Color( COL_TRANSPARENT ) && |
| m_aCurrentPDFState.m_aFillColor != Color( COL_TRANSPARENT ) ) |
| aContent.append( " B*\n" ); |
| else if( m_aCurrentPDFState.m_aLineColor != Color( COL_TRANSPARENT ) ) |
| aContent.append( " S\n" ); |
| else |
| aContent.append( " f*\n" ); |
| m_aTransparentObjects.back().m_pContentStream->Write( aContent.getStr(), aContent.getLength() ); |
| |
| OStringBuffer aObjName( 16 ); |
| aObjName.append( "Tr" ); |
| aObjName.append( m_aTransparentObjects.back().m_nObject ); |
| OString aTrName( aObjName.makeStringAndClear() ); |
| aObjName.append( "EGS" ); |
| aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject ); |
| OString aExtName( aObjName.makeStringAndClear() ); |
| |
| OStringBuffer aLine( 80 ); |
| // insert XObject |
| aLine.append( "q /" ); |
| aLine.append( aExtName ); |
| aLine.append( " gs /" ); |
| aLine.append( aTrName ); |
| aLine.append( " Do Q\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| |
| pushResource( ResXObject, aTrName, m_aTransparentObjects.back().m_nObject ); |
| pushResource( ResExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject ); |
| } |
| |
| void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject ) |
| { |
| if( nObject >= 0 ) |
| { |
| switch( eKind ) |
| { |
| case ResXObject: |
| m_aGlobalResourceDict.m_aXObjects[ rResource ] = nObject; |
| if( ! m_aOutputStreams.empty() ) |
| m_aOutputStreams.front().m_aResourceDict.m_aXObjects[ rResource ] = nObject; |
| break; |
| case ResExtGState: |
| m_aGlobalResourceDict.m_aExtGStates[ rResource ] = nObject; |
| if( ! m_aOutputStreams.empty() ) |
| m_aOutputStreams.front().m_aResourceDict.m_aExtGStates[ rResource ] = nObject; |
| break; |
| case ResShading: |
| m_aGlobalResourceDict.m_aShadings[ rResource ] = nObject; |
| if( ! m_aOutputStreams.empty() ) |
| m_aOutputStreams.front().m_aResourceDict.m_aShadings[ rResource ] = nObject; |
| break; |
| case ResPattern: |
| m_aGlobalResourceDict.m_aPatterns[ rResource ] = nObject; |
| if( ! m_aOutputStreams.empty() ) |
| m_aOutputStreams.front().m_aResourceDict.m_aPatterns[ rResource ] = nObject; |
| break; |
| } |
| } |
| } |
| |
| void PDFWriterImpl::beginRedirect( SvStream* pStream, const Rectangle& rTargetRect ) |
| { |
| push( PUSH_ALL ); |
| |
| // force reemitting clip region |
| clearClipRegion(); |
| updateGraphicsState(); |
| |
| m_aOutputStreams.push_front( StreamRedirect() ); |
| m_aOutputStreams.front().m_pStream = pStream; |
| m_aOutputStreams.front().m_aMapMode = m_aMapMode; |
| |
| if( !rTargetRect.IsEmpty() ) |
| { |
| m_aOutputStreams.front().m_aTargetRect = |
| lcl_convert( m_aGraphicsStack.front().m_aMapMode, |
| m_aMapMode, |
| getReferenceDevice(), |
| rTargetRect ); |
| Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft(); |
| long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight()); |
| aDelta.Y() = -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()); |
| m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta ); |
| } |
| |
| // setup graphics state for independent object stream |
| |
| // force reemitting colors |
| m_aCurrentPDFState.m_aLineColor = Color( COL_TRANSPARENT ); |
| m_aCurrentPDFState.m_aFillColor = Color( COL_TRANSPARENT ); |
| } |
| |
| Rectangle PDFWriterImpl::getRedirectTargetRect() const |
| { |
| return m_aOutputStreams.empty() ? Rectangle() : m_aOutputStreams.front().m_aTargetRect; |
| } |
| |
| SvStream* PDFWriterImpl::endRedirect() |
| { |
| SvStream* pStream = NULL; |
| if( ! m_aOutputStreams.empty() ) |
| { |
| pStream = m_aOutputStreams.front().m_pStream; |
| m_aMapMode = m_aOutputStreams.front().m_aMapMode; |
| m_aOutputStreams.pop_front(); |
| } |
| |
| pop(); |
| // force reemitting colors and clip region |
| clearClipRegion(); |
| m_aCurrentPDFState.m_bClipRegion = m_aGraphicsStack.front().m_bClipRegion; |
| m_aCurrentPDFState.m_aClipRegion = m_aGraphicsStack.front().m_aClipRegion; |
| m_aCurrentPDFState.m_aLineColor = Color( COL_TRANSPARENT ); |
| m_aCurrentPDFState.m_aFillColor = Color( COL_TRANSPARENT ); |
| |
| updateGraphicsState(); |
| |
| return pStream; |
| } |
| |
| void PDFWriterImpl::beginTransparencyGroup() |
| { |
| updateGraphicsState(); |
| if( m_aContext.Version >= PDFWriter::PDF_1_4 ) |
| beginRedirect( new SvMemoryStream( 1024, 1024 ), Rectangle() ); |
| } |
| |
| void PDFWriterImpl::endTransparencyGroup( const Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent ) |
| { |
| DBG_ASSERT( nTransparentPercent <= 100, "invalid alpha value" ); |
| nTransparentPercent = nTransparentPercent % 100; |
| |
| if( m_aContext.Version >= PDFWriter::PDF_1_4 ) |
| { |
| // create XObject |
| m_aTransparentObjects.push_back( TransparencyEmit() ); |
| m_aTransparentObjects.back().m_aBoundRect = rBoundingBox; |
| // convert rectangle to default user space |
| m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect ); |
| m_aTransparentObjects.back().m_nObject = createObject(); |
| m_aTransparentObjects.back().m_fAlpha = (double)(100-nTransparentPercent) / 100.0; |
| // get XObject's content stream |
| m_aTransparentObjects.back().m_pContentStream = static_cast<SvMemoryStream*>(endRedirect()); |
| m_aTransparentObjects.back().m_nExtGStateObject = createObject(); |
| |
| OStringBuffer aObjName( 16 ); |
| aObjName.append( "Tr" ); |
| aObjName.append( m_aTransparentObjects.back().m_nObject ); |
| OString aTrName( aObjName.makeStringAndClear() ); |
| aObjName.append( "EGS" ); |
| aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject ); |
| OString aExtName( aObjName.makeStringAndClear() ); |
| |
| OStringBuffer aLine( 80 ); |
| // insert XObject |
| aLine.append( "q /" ); |
| aLine.append( aExtName ); |
| aLine.append( " gs /" ); |
| aLine.append( aTrName ); |
| aLine.append( " Do Q\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| |
| pushResource( ResXObject, aTrName, m_aTransparentObjects.back().m_nObject ); |
| pushResource( ResExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject ); |
| } |
| } |
| |
| void PDFWriterImpl::endTransparencyGroup( const Rectangle& rBoundingBox, const Bitmap& rAlphaMask ) |
| { |
| if( m_aContext.Version >= PDFWriter::PDF_1_4 ) |
| { |
| // create XObject |
| m_aTransparentObjects.push_back( TransparencyEmit() ); |
| m_aTransparentObjects.back().m_aBoundRect = rBoundingBox; |
| // convert rectangle to default user space |
| m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect ); |
| m_aTransparentObjects.back().m_nObject = createObject(); |
| m_aTransparentObjects.back().m_fAlpha = 0.0; |
| // get XObject's content stream |
| m_aTransparentObjects.back().m_pContentStream = static_cast<SvMemoryStream*>(endRedirect()); |
| m_aTransparentObjects.back().m_nExtGStateObject = createObject(); |
| |
| // draw soft mask |
| beginRedirect( new SvMemoryStream( 1024, 1024 ), Rectangle() ); |
| drawBitmap( rBoundingBox.TopLeft(), rBoundingBox.GetSize(), rAlphaMask ); |
| m_aTransparentObjects.back().m_pSoftMaskStream = static_cast<SvMemoryStream*>(endRedirect()); |
| |
| OStringBuffer aObjName( 16 ); |
| aObjName.append( "Tr" ); |
| aObjName.append( m_aTransparentObjects.back().m_nObject ); |
| OString aTrName( aObjName.makeStringAndClear() ); |
| aObjName.append( "EGS" ); |
| aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject ); |
| OString aExtName( aObjName.makeStringAndClear() ); |
| |
| OStringBuffer aLine( 80 ); |
| // insert XObject |
| aLine.append( "q /" ); |
| aLine.append( aExtName ); |
| aLine.append( " gs /" ); |
| aLine.append( aTrName ); |
| aLine.append( " Do Q\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| |
| pushResource( ResXObject, aTrName, m_aTransparentObjects.back().m_nObject ); |
| pushResource( ResExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject ); |
| } |
| } |
| |
| void PDFWriterImpl::drawRectangle( const Rectangle& rRect ) |
| { |
| MARK( "drawRectangle" ); |
| |
| updateGraphicsState(); |
| |
| if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && |
| m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) ) |
| return; |
| |
| OStringBuffer aLine( 40 ); |
| m_aPages.back().appendRect( rRect, aLine ); |
| |
| if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) && |
| m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) ) |
| aLine.append( " B*\n" ); |
| else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) |
| aLine.append( " S\n" ); |
| else |
| aLine.append( " f*\n" ); |
| |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| void PDFWriterImpl::drawRectangle( const Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound ) |
| { |
| MARK( "drawRectangle with rounded edges" ); |
| |
| if( !nHorzRound && !nVertRound ) |
| drawRectangle( rRect ); |
| |
| updateGraphicsState(); |
| |
| if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && |
| m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) ) |
| return; |
| |
| if( nHorzRound > (sal_uInt32)rRect.GetWidth()/2 ) |
| nHorzRound = rRect.GetWidth()/2; |
| if( nVertRound > (sal_uInt32)rRect.GetHeight()/2 ) |
| nVertRound = rRect.GetHeight()/2; |
| |
| Point aPoints[16]; |
| const double kappa = 0.5522847498; |
| const sal_uInt32 kx = (sal_uInt32)((kappa*(double)nHorzRound)+0.5); |
| const sal_uInt32 ky = (sal_uInt32)((kappa*(double)nVertRound)+0.5); |
| |
| aPoints[1] = Point( rRect.TopLeft().X() + nHorzRound, rRect.TopLeft().Y() ); |
| aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() ); |
| aPoints[2] = Point( rRect.TopRight().X()+1 - nHorzRound, aPoints[1].Y() ); |
| aPoints[3] = Point( aPoints[2].X()+kx, aPoints[2].Y() ); |
| |
| aPoints[5] = Point( rRect.TopRight().X()+1, rRect.TopRight().Y()+nVertRound ); |
| aPoints[4] = Point( aPoints[5].X(), aPoints[5].Y()-ky ); |
| aPoints[6] = Point( aPoints[5].X(), rRect.BottomRight().Y()+1 - nVertRound ); |
| aPoints[7] = Point( aPoints[6].X(), aPoints[6].Y()+ky ); |
| |
| aPoints[9] = Point( rRect.BottomRight().X()+1-nHorzRound, rRect.BottomRight().Y()+1 ); |
| aPoints[8] = Point( aPoints[9].X()+kx, aPoints[9].Y() ); |
| aPoints[10] = Point( rRect.BottomLeft().X() + nHorzRound, aPoints[9].Y() ); |
| aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() ); |
| |
| aPoints[13] = Point( rRect.BottomLeft().X(), rRect.BottomLeft().Y()+1-nVertRound ); |
| aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky ); |
| aPoints[14] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y()+nVertRound ); |
| aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky ); |
| |
| |
| OStringBuffer aLine( 80 ); |
| m_aPages.back().appendPoint( aPoints[1], aLine ); |
| aLine.append( " m " ); |
| m_aPages.back().appendPoint( aPoints[2], aLine ); |
| aLine.append( " l " ); |
| m_aPages.back().appendPoint( aPoints[3], aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( aPoints[4], aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( aPoints[5], aLine ); |
| aLine.append( " c\n" ); |
| m_aPages.back().appendPoint( aPoints[6], aLine ); |
| aLine.append( " l " ); |
| m_aPages.back().appendPoint( aPoints[7], aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( aPoints[8], aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( aPoints[9], aLine ); |
| aLine.append( " c\n" ); |
| m_aPages.back().appendPoint( aPoints[10], aLine ); |
| aLine.append( " l " ); |
| m_aPages.back().appendPoint( aPoints[11], aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( aPoints[12], aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( aPoints[13], aLine ); |
| aLine.append( " c\n" ); |
| m_aPages.back().appendPoint( aPoints[14], aLine ); |
| aLine.append( " l " ); |
| m_aPages.back().appendPoint( aPoints[15], aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( aPoints[0], aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( aPoints[1], aLine ); |
| aLine.append( " c " ); |
| |
| if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) && |
| m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) ) |
| aLine.append( "b*\n" ); |
| else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) |
| aLine.append( "s\n" ); |
| else |
| aLine.append( "f*\n" ); |
| |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| void PDFWriterImpl::drawEllipse( const Rectangle& rRect ) |
| { |
| MARK( "drawEllipse" ); |
| |
| updateGraphicsState(); |
| |
| if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && |
| m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) ) |
| return; |
| |
| Point aPoints[12]; |
| const double kappa = 0.5522847498; |
| const sal_uInt32 kx = (sal_uInt32)((kappa*(double)rRect.GetWidth()/2.0)+0.5); |
| const sal_uInt32 ky = (sal_uInt32)((kappa*(double)rRect.GetHeight()/2.0)+0.5); |
| |
| aPoints[1] = Point( rRect.TopLeft().X() + rRect.GetWidth()/2, rRect.TopLeft().Y() ); |
| aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() ); |
| aPoints[2] = Point( aPoints[1].X() + kx, aPoints[1].Y() ); |
| |
| aPoints[4] = Point( rRect.TopRight().X()+1, rRect.TopRight().Y() + rRect.GetHeight()/2 ); |
| aPoints[3] = Point( aPoints[4].X(), aPoints[4].Y() - ky ); |
| aPoints[5] = Point( aPoints[4].X(), aPoints[4].Y() + ky ); |
| |
| aPoints[7] = Point( rRect.BottomLeft().X() + rRect.GetWidth()/2, rRect.BottomLeft().Y()+1 ); |
| aPoints[6] = Point( aPoints[7].X() + kx, aPoints[7].Y() ); |
| aPoints[8] = Point( aPoints[7].X() - kx, aPoints[7].Y() ); |
| |
| aPoints[10] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y() + rRect.GetHeight()/2 ); |
| aPoints[9] = Point( aPoints[10].X(), aPoints[10].Y() + ky ); |
| aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky ); |
| |
| OStringBuffer aLine( 80 ); |
| m_aPages.back().appendPoint( aPoints[1], aLine ); |
| aLine.append( " m " ); |
| m_aPages.back().appendPoint( aPoints[2], aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( aPoints[3], aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( aPoints[4], aLine ); |
| aLine.append( " c\n" ); |
| m_aPages.back().appendPoint( aPoints[5], aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( aPoints[6], aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( aPoints[7], aLine ); |
| aLine.append( " c\n" ); |
| m_aPages.back().appendPoint( aPoints[8], aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( aPoints[9], aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( aPoints[10], aLine ); |
| aLine.append( " c\n" ); |
| m_aPages.back().appendPoint( aPoints[11], aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( aPoints[0], aLine ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( aPoints[1], aLine ); |
| aLine.append( " c " ); |
| |
| if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) && |
| m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) ) |
| aLine.append( "b*\n" ); |
| else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) |
| aLine.append( "s\n" ); |
| else |
| aLine.append( "f*\n" ); |
| |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| static double calcAngle( const Rectangle& rRect, const Point& rPoint ) |
| { |
| Point aOrigin((rRect.Left()+rRect.Right()+1)/2, |
| (rRect.Top()+rRect.Bottom()+1)/2); |
| Point aPoint = rPoint - aOrigin; |
| |
| double fX = (double)aPoint.X(); |
| double fY = (double)-aPoint.Y(); |
| |
| if( rRect.GetWidth() > rRect.GetHeight() ) |
| fY = fY*((double)rRect.GetWidth()/(double)rRect.GetHeight()); |
| else if( rRect.GetHeight() > rRect.GetWidth() ) |
| fX = fX*((double)rRect.GetHeight()/(double)rRect.GetWidth()); |
| return atan2( fY, fX ); |
| } |
| |
| void PDFWriterImpl::drawArc( const Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord ) |
| { |
| MARK( "drawArc" ); |
| |
| updateGraphicsState(); |
| |
| if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && |
| m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) ) |
| return; |
| |
| // calculate start and stop angles |
| const double fStartAngle = calcAngle( rRect, rStart ); |
| double fStopAngle = calcAngle( rRect, rStop ); |
| while( fStopAngle < fStartAngle ) |
| fStopAngle += 2.0*M_PI; |
| const int nFragments = (int)((fStopAngle-fStartAngle)/(M_PI/2.0))+1; |
| const double fFragmentDelta = (fStopAngle-fStartAngle)/(double)nFragments; |
| const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0); |
| const double halfWidth = (double)rRect.GetWidth()/2.0; |
| const double halfHeight = (double)rRect.GetHeight()/2.0; |
| |
| const Point aCenter( (rRect.Left()+rRect.Right()+1)/2, |
| (rRect.Top()+rRect.Bottom()+1)/2 ); |
| |
| OStringBuffer aLine( 30*nFragments ); |
| Point aPoint( (int)(halfWidth * cos(fStartAngle) ), |
| -(int)(halfHeight * sin(fStartAngle) ) ); |
| aPoint += aCenter; |
| m_aPages.back().appendPoint( aPoint, aLine ); |
| aLine.append( " m " ); |
| if( !basegfx::fTools::equal(fStartAngle, fStopAngle) ) |
| { |
| for( int i = 0; i < nFragments; i++ ) |
| { |
| const double fStartFragment = fStartAngle + (double)i*fFragmentDelta; |
| const double fStopFragment = fStartFragment + fFragmentDelta; |
| aPoint = Point( (int)(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ), |
| -(int)(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) ); |
| aPoint += aCenter; |
| m_aPages.back().appendPoint( aPoint, aLine ); |
| aLine.append( ' ' ); |
| |
| aPoint = Point( (int)(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ), |
| -(int)(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) ); |
| aPoint += aCenter; |
| m_aPages.back().appendPoint( aPoint, aLine ); |
| aLine.append( ' ' ); |
| |
| aPoint = Point( (int)(halfWidth * cos(fStopFragment) ), |
| -(int)(halfHeight * sin(fStopFragment) ) ); |
| aPoint += aCenter; |
| m_aPages.back().appendPoint( aPoint, aLine ); |
| aLine.append( " c\n" ); |
| } |
| } |
| if( bWithChord || bWithPie ) |
| { |
| if( bWithPie ) |
| { |
| m_aPages.back().appendPoint( aCenter, aLine ); |
| aLine.append( " l " ); |
| } |
| aLine.append( "h " ); |
| } |
| if( ! bWithChord && ! bWithPie ) |
| aLine.append( "S\n" ); |
| else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) && |
| m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) ) |
| aLine.append( "B*\n" ); |
| else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) |
| aLine.append( "S\n" ); |
| else |
| aLine.append( "f*\n" ); |
| |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| void PDFWriterImpl::drawPolyLine( const Polygon& rPoly ) |
| { |
| MARK( "drawPolyLine" ); |
| |
| sal_uInt16 nPoints = rPoly.GetSize(); |
| if( nPoints < 2 ) |
| return; |
| |
| updateGraphicsState(); |
| |
| if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) ) |
| return; |
| |
| OStringBuffer aLine( 20 * nPoints ); |
| m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] ); |
| aLine.append( "S\n" ); |
| |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| void PDFWriterImpl::drawPolyLine( const Polygon& rPoly, const LineInfo& rInfo ) |
| { |
| MARK( "drawPolyLine with LineInfo" ); |
| |
| updateGraphicsState(); |
| |
| if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) ) |
| return; |
| |
| OStringBuffer aLine; |
| aLine.append( "q " ); |
| if( m_aPages.back().appendLineInfo( rInfo, aLine ) ) |
| { |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| drawPolyLine( rPoly ); |
| writeBuffer( "Q\n", 2 ); |
| } |
| else |
| { |
| PDFWriter::ExtLineInfo aInfo; |
| convertLineInfoToExtLineInfo( rInfo, aInfo ); |
| drawPolyLine( rPoly, aInfo ); |
| } |
| } |
| |
| void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut ) |
| { |
| DBG_ASSERT( rIn.GetStyle() == LINE_DASH, "invalid conversion" ); |
| rOut.m_fLineWidth = rIn.GetWidth(); |
| rOut.m_fTransparency = 0.0; |
| rOut.m_eCap = PDFWriter::capButt; |
| rOut.m_eJoin = PDFWriter::joinMiter; |
| rOut.m_fMiterLimit = 10; |
| rOut.m_aDashArray.clear(); |
| |
| // add DashDot to DashArray |
| const int nDashes = rIn.GetDashCount(); |
| const int nDashLen = rIn.GetDashLen(); |
| const int nDistance = rIn.GetDistance(); |
| |
| for( int n = 0; n < nDashes; n++ ) |
| { |
| rOut.m_aDashArray.push_back( nDashLen ); |
| rOut.m_aDashArray.push_back( nDistance ); |
| } |
| |
| const int nDots = rIn.GetDotCount(); |
| const int nDotLen = rIn.GetDotLen(); |
| |
| for( int n = 0; n < nDots; n++ ) |
| { |
| rOut.m_aDashArray.push_back( nDotLen ); |
| rOut.m_aDashArray.push_back( nDistance ); |
| } |
| |
| // add LineJoin |
| switch(rIn.GetLineJoin()) |
| { |
| case basegfx::B2DLINEJOIN_BEVEL : |
| { |
| rOut.m_eJoin = PDFWriter::joinBevel; |
| break; |
| } |
| default : // basegfx::B2DLINEJOIN_NONE : |
| // Pdf has no 'none' lineJoin, default is miter |
| case basegfx::B2DLINEJOIN_MIDDLE : |
| case basegfx::B2DLINEJOIN_MITER : |
| { |
| rOut.m_eJoin = PDFWriter::joinMiter; |
| break; |
| } |
| case basegfx::B2DLINEJOIN_ROUND : |
| { |
| rOut.m_eJoin = PDFWriter::joinRound; |
| break; |
| } |
| } |
| |
| // add LineCap |
| switch(rIn.GetLineCap()) |
| { |
| default: /* com::sun::star::drawing::LineCap_BUTT */ |
| { |
| rOut.m_eCap = PDFWriter::capButt; |
| break; |
| } |
| case com::sun::star::drawing::LineCap_ROUND: |
| { |
| rOut.m_eCap = PDFWriter::capRound; |
| break; |
| } |
| case com::sun::star::drawing::LineCap_SQUARE: |
| { |
| rOut.m_eCap = PDFWriter::capSquare; |
| break; |
| } |
| } |
| } |
| |
| void PDFWriterImpl::drawPolyLine( const Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo ) |
| { |
| MARK( "drawPolyLine with ExtLineInfo" ); |
| |
| updateGraphicsState(); |
| |
| if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) ) |
| return; |
| |
| if( rInfo.m_fTransparency >= 1.0 ) |
| return; |
| |
| if( rInfo.m_fTransparency != 0.0 ) |
| beginTransparencyGroup(); |
| |
| OStringBuffer aLine; |
| aLine.append( "q " ); |
| m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine ); |
| aLine.append( " w" ); |
| if( rInfo.m_aDashArray.size() < 10 ) // implmentation limit of acrobat reader |
| { |
| switch( rInfo.m_eCap ) |
| { |
| default: |
| case PDFWriter::capButt: aLine.append( " 0 J" );break; |
| case PDFWriter::capRound: aLine.append( " 1 J" );break; |
| case PDFWriter::capSquare: aLine.append( " 2 J" );break; |
| } |
| switch( rInfo.m_eJoin ) |
| { |
| default: |
| case PDFWriter::joinMiter: |
| { |
| double fLimit = rInfo.m_fMiterLimit; |
| if( rInfo.m_fLineWidth < rInfo.m_fMiterLimit ) |
| fLimit = fLimit / rInfo.m_fLineWidth; |
| if( fLimit < 1.0 ) |
| fLimit = 1.0; |
| aLine.append( " 0 j " ); |
| appendDouble( fLimit, aLine ); |
| aLine.append( " M" ); |
| } |
| break; |
| case PDFWriter::joinRound: aLine.append( " 1 j" );break; |
| case PDFWriter::joinBevel: aLine.append( " 2 j" );break; |
| } |
| if( rInfo.m_aDashArray.size() > 0 ) |
| { |
| aLine.append( " [ " ); |
| for( std::vector<double>::const_iterator it = rInfo.m_aDashArray.begin(); |
| it != rInfo.m_aDashArray.end(); ++it ) |
| { |
| m_aPages.back().appendMappedLength( *it, aLine ); |
| aLine.append( ' ' ); |
| } |
| aLine.append( "] 0 d" ); |
| } |
| aLine.append( "\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| drawPolyLine( rPoly ); |
| } |
| else |
| { |
| basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon()); |
| basegfx::B2DPolyPolygon aPolyPoly; |
| |
| basegfx::tools::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly); |
| |
| // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments. |
| // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality) |
| // this line needs to be removed and the loop below adapted accordingly |
| aPolyPoly = basegfx::tools::adaptiveSubdivideByAngle(aPolyPoly); |
| |
| const sal_uInt32 nPolygonCount(aPolyPoly.count()); |
| |
| for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ ) |
| { |
| aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " ); |
| aPoly = aPolyPoly.getB2DPolygon( nPoly ); |
| const sal_uInt32 nPointCount(aPoly.count()); |
| |
| if(nPointCount) |
| { |
| const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1); |
| basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0)); |
| |
| for(sal_uInt32 a(0); a < nEdgeCount; a++) |
| { |
| if( a > 0 ) |
| aLine.append( " " ); |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex)); |
| |
| m_aPages.back().appendPoint( Point( FRound(aCurrent.getX()), |
| FRound(aCurrent.getY()) ), |
| aLine ); |
| aLine.append( " m " ); |
| m_aPages.back().appendPoint( Point( FRound(aNext.getX()), |
| FRound(aNext.getY()) ), |
| aLine ); |
| aLine.append( " l" ); |
| |
| // prepare next edge |
| aCurrent = aNext; |
| } |
| } |
| } |
| aLine.append( " S " ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| writeBuffer( "Q\n", 2 ); |
| |
| if( rInfo.m_fTransparency != 0.0 ) |
| { |
| // FIXME: actually this may be incorrect with bezier polygons |
| Rectangle aBoundRect( rPoly.GetBoundRect() ); |
| // avoid clipping with thick lines |
| if( rInfo.m_fLineWidth > 0.0 ) |
| { |
| sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth); |
| aBoundRect.Top() -= nLW; |
| aBoundRect.Left() -= nLW; |
| aBoundRect.Right() += nLW; |
| aBoundRect.Bottom() += nLW; |
| } |
| endTransparencyGroup( aBoundRect, (sal_uInt16)(100.0*rInfo.m_fTransparency) ); |
| } |
| } |
| |
| void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor ) |
| { |
| MARK( "drawPixel" ); |
| |
| Color aColor = ( rColor == Color( COL_TRANSPARENT ) ? m_aGraphicsStack.front().m_aLineColor : rColor ); |
| |
| if( aColor == Color( COL_TRANSPARENT ) ) |
| return; |
| |
| // pixels are drawn in line color, so have to set |
| // the nonstroking color to line color |
| Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor; |
| setFillColor( aColor ); |
| |
| updateGraphicsState(); |
| |
| OStringBuffer aLine( 20 ); |
| m_aPages.back().appendPoint( rPoint, aLine ); |
| aLine.append( ' ' ); |
| appendDouble( 1.0/double(getReferenceDevice()->ImplGetDPIX()), aLine ); |
| aLine.append( ' ' ); |
| appendDouble( 1.0/double(getReferenceDevice()->ImplGetDPIY()), aLine ); |
| aLine.append( " re f\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| |
| setFillColor( aOldFillColor ); |
| } |
| |
| void PDFWriterImpl::drawPixel( const Polygon& rPoints, const Color* pColors ) |
| { |
| MARK( "drawPixel with Polygon" ); |
| |
| updateGraphicsState(); |
| |
| if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && ! pColors ) |
| return; |
| |
| sal_uInt16 nPoints = rPoints.GetSize(); |
| OStringBuffer aLine( nPoints*40 ); |
| aLine.append( "q " ); |
| if( ! pColors ) |
| { |
| appendNonStrokingColor( m_aGraphicsStack.front().m_aLineColor, aLine ); |
| aLine.append( ' ' ); |
| } |
| |
| OStringBuffer aPixel(32); |
| aPixel.append( ' ' ); |
| appendDouble( 1.0/double(getReferenceDevice()->ImplGetDPIX()), aPixel ); |
| aPixel.append( ' ' ); |
| appendDouble( 1.0/double(getReferenceDevice()->ImplGetDPIY()), aPixel ); |
| OString aPixelStr = aPixel.makeStringAndClear(); |
| for( sal_uInt16 i = 0; i < nPoints; i++ ) |
| { |
| if( pColors ) |
| { |
| if( pColors[i] == Color( COL_TRANSPARENT ) ) |
| continue; |
| |
| appendNonStrokingColor( pColors[i], aLine ); |
| aLine.append( ' ' ); |
| } |
| m_aPages.back().appendPoint( rPoints[i], aLine ); |
| aLine.append( aPixelStr ); |
| aLine.append( " re f\n" ); |
| } |
| aLine.append( "Q\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| class AccessReleaser |
| { |
| BitmapReadAccess* m_pAccess; |
| public: |
| AccessReleaser( BitmapReadAccess* pAccess ) : m_pAccess( pAccess ){} |
| ~AccessReleaser() { delete m_pAccess; } |
| }; |
| |
| bool PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject ) |
| { |
| CHECK_RETURN( updateObject( rObject.m_nObject ) ); |
| |
| bool bFlateFilter = compressStream( rObject.m_pContentStream ); |
| rObject.m_pContentStream->Seek( STREAM_SEEK_TO_END ); |
| sal_uLong nSize = rObject.m_pContentStream->Tell(); |
| rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN ); |
| #if OSL_DEBUG_LEVEL > 1 |
| emitComment( "PDFWriterImpl::writeTransparentObject" ); |
| #endif |
| OStringBuffer aLine( 512 ); |
| CHECK_RETURN( updateObject( rObject.m_nObject ) ); |
| aLine.append( rObject.m_nObject ); |
| aLine.append( " 0 obj\n" |
| "<</Type/XObject\n" |
| "/Subtype/Form\n" |
| "/BBox[ " ); |
| appendFixedInt( rObject.m_aBoundRect.Left(), aLine ); |
| aLine.append( ' ' ); |
| appendFixedInt( rObject.m_aBoundRect.Top(), aLine ); |
| aLine.append( ' ' ); |
| appendFixedInt( rObject.m_aBoundRect.Right(), aLine ); |
| aLine.append( ' ' ); |
| appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine ); |
| aLine.append( " ]\n" ); |
| if( ! rObject.m_pSoftMaskStream ) |
| { |
| if( ! m_bIsPDF_A1 ) |
| { |
| aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" ); |
| } |
| } |
| /* #i42884# the PDF reference recommends that each Form XObject |
| * should have a resource dict; alas if that is the same object |
| * as the one of the page it triggers an endless recursion in |
| * acroread 5 (6 and up have that fixed). Since we have only one |
| * resource dict anyway, let's use the one from the page by NOT |
| * emitting a Resources entry. |
| */ |
| #if 0 |
| aLine.append( " /Resources " ); |
| aLine.append( getResourceDictObj() ); |
| aLine.append( " 0 R\n" ); |
| #endif |
| |
| aLine.append( "/Length " ); |
| aLine.append( (sal_Int32)(nSize) ); |
| aLine.append( "\n" ); |
| if( bFlateFilter ) |
| aLine.append( "/Filter/FlateDecode\n" ); |
| aLine.append( ">>\n" |
| "stream\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| checkAndEnableStreamEncryption( rObject.m_nObject ); |
| CHECK_RETURN( writeBuffer( rObject.m_pContentStream->GetData(), nSize ) ); |
| disableStreamEncryption(); |
| aLine.setLength( 0 ); |
| aLine.append( "\n" |
| "endstream\n" |
| "endobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| // write ExtGState dict for this XObject |
| aLine.setLength( 0 ); |
| aLine.append( rObject.m_nExtGStateObject ); |
| aLine.append( " 0 obj\n" |
| "<<" ); |
| if( ! rObject.m_pSoftMaskStream ) |
| { |
| //i59651 |
| if( m_bIsPDF_A1 ) |
| { |
| aLine.append( "/CA 1.0/ca 1.0" ); |
| m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); |
| } |
| else |
| { |
| aLine.append( "/CA " ); |
| appendDouble( rObject.m_fAlpha, aLine ); |
| aLine.append( "\n" |
| " /ca " ); |
| appendDouble( rObject.m_fAlpha, aLine ); |
| } |
| aLine.append( "\n" ); |
| } |
| else |
| { |
| if( m_bIsPDF_A1 ) |
| { |
| aLine.append( "/SMask/None" ); |
| m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); |
| } |
| else |
| { |
| rObject.m_pSoftMaskStream->Seek( STREAM_SEEK_TO_END ); |
| sal_Int32 nMaskSize = (sal_Int32)rObject.m_pSoftMaskStream->Tell(); |
| rObject.m_pSoftMaskStream->Seek( STREAM_SEEK_TO_BEGIN ); |
| sal_Int32 nMaskObject = createObject(); |
| aLine.append( "/SMask<</Type/Mask/S/Luminosity/G " ); |
| aLine.append( nMaskObject ); |
| aLine.append( " 0 R>>\n" ); |
| |
| OStringBuffer aMask; |
| aMask.append( nMaskObject ); |
| aMask.append( " 0 obj\n" |
| "<</Type/XObject\n" |
| "/Subtype/Form\n" |
| "/BBox[" ); |
| appendFixedInt( rObject.m_aBoundRect.Left(), aMask ); |
| aMask.append( ' ' ); |
| appendFixedInt( rObject.m_aBoundRect.Top(), aMask ); |
| aMask.append( ' ' ); |
| appendFixedInt( rObject.m_aBoundRect.Right(), aMask ); |
| aMask.append( ' ' ); |
| appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aMask ); |
| aMask.append( "]\n" ); |
| |
| /* #i42884# see above */ |
| #if 0 |
| aLine.append( "/Resources " ); |
| aMask.append( getResourceDictObj() ); |
| aMask.append( " 0 R\n" ); |
| #endif |
| |
| aMask.append( "/Group<</S/Transparency/CS/DeviceRGB>>\n" ); |
| aMask.append( "/Length " ); |
| aMask.append( nMaskSize ); |
| aMask.append( ">>\n" |
| "stream\n" ); |
| CHECK_RETURN( updateObject( nMaskObject ) ); |
| checkAndEnableStreamEncryption( nMaskObject ); |
| CHECK_RETURN( writeBuffer( aMask.getStr(), aMask.getLength() ) ); |
| CHECK_RETURN( writeBuffer( rObject.m_pSoftMaskStream->GetData(), nMaskSize ) ); |
| disableStreamEncryption(); |
| aMask.setLength( 0 ); |
| aMask.append( "\nendstream\n" |
| "endobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aMask.getStr(), aMask.getLength() ) ); |
| } |
| } |
| aLine.append( ">>\n" |
| "endobj\n\n" ); |
| CHECK_RETURN( updateObject( rObject.m_nExtGStateObject ) ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| return true; |
| } |
| |
| bool PDFWriterImpl::writeGradientFunction( GradientEmit& rObject ) |
| { |
| sal_Int32 nFunctionObject = createObject(); |
| CHECK_RETURN( updateObject( nFunctionObject ) ); |
| |
| VirtualDevice aDev; |
| aDev.SetOutputSizePixel( rObject.m_aSize ); |
| aDev.SetMapMode( MapMode( MAP_PIXEL ) ); |
| if( m_aContext.ColorMode == PDFWriter::DrawGreyscale ) |
| aDev.SetDrawMode( aDev.GetDrawMode() | |
| ( DRAWMODE_GRAYLINE | DRAWMODE_GRAYFILL | DRAWMODE_GRAYTEXT | |
| DRAWMODE_GRAYBITMAP | DRAWMODE_GRAYGRADIENT ) ); |
| aDev.DrawGradient( Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient ); |
| |
| Bitmap aSample = aDev.GetBitmap( Point( 0, 0 ), rObject.m_aSize ); |
| BitmapReadAccess* pAccess = aSample.AcquireReadAccess(); |
| AccessReleaser aReleaser( pAccess ); |
| |
| Size aSize = aSample.GetSizePixel(); |
| |
| sal_Int32 nStreamLengthObject = createObject(); |
| #if OSL_DEBUG_LEVEL > 1 |
| emitComment( "PDFWriterImpl::writeGradientFunction" ); |
| #endif |
| OStringBuffer aLine( 120 ); |
| aLine.append( nFunctionObject ); |
| aLine.append( " 0 obj\n" |
| "<</FunctionType 0\n" |
| "/Domain[ 0 1 0 1 ]\n" |
| "/Size[ " ); |
| aLine.append( (sal_Int32)aSize.Width() ); |
| aLine.append( ' ' ); |
| aLine.append( (sal_Int32)aSize.Height() ); |
| aLine.append( " ]\n" |
| "/BitsPerSample 8\n" |
| "/Range[ 0 1 0 1 0 1 ]\n" |
| "/Order 3\n" |
| "/Length " ); |
| aLine.append( nStreamLengthObject ); |
| aLine.append( " 0 R\n" |
| #ifndef DEBUG_DISABLE_PDFCOMPRESSION |
| "/Filter/FlateDecode" |
| #endif |
| ">>\n" |
| "stream\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| sal_uInt64 nStartStreamPos = 0; |
| CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nStartStreamPos )) ); |
| |
| checkAndEnableStreamEncryption( nFunctionObject ); |
| beginCompression(); |
| for( int y = aSize.Height()-1; y >= 0; y-- ) |
| { |
| for( int x = 0; x < aSize.Width(); x++ ) |
| { |
| sal_uInt8 aCol[3]; |
| BitmapColor aColor = pAccess->GetColor( y, x ); |
| aCol[0] = aColor.GetRed(); |
| aCol[1] = aColor.GetGreen(); |
| aCol[2] = aColor.GetBlue(); |
| CHECK_RETURN( writeBuffer( aCol, 3 ) ); |
| } |
| } |
| endCompression(); |
| disableStreamEncryption(); |
| |
| sal_uInt64 nEndStreamPos = 0; |
| CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nEndStreamPos )) ); |
| |
| aLine.setLength( 0 ); |
| aLine.append( "\nendstream\nendobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| // write stream length |
| CHECK_RETURN( updateObject( nStreamLengthObject ) ); |
| aLine.setLength( 0 ); |
| aLine.append( nStreamLengthObject ); |
| aLine.append( " 0 obj\n" ); |
| aLine.append( (sal_Int64)(nEndStreamPos-nStartStreamPos) ); |
| aLine.append( "\nendobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| CHECK_RETURN( updateObject( rObject.m_nObject ) ); |
| aLine.setLength( 0 ); |
| aLine.append( rObject.m_nObject ); |
| aLine.append( " 0 obj\n" |
| "<</ShadingType 1\n" |
| "/ColorSpace/DeviceRGB\n" |
| "/AntiAlias true\n" |
| "/Domain[ 0 1 0 1 ]\n" |
| "/Matrix[ " ); |
| aLine.append( (sal_Int32)aSize.Width() ); |
| aLine.append( " 0 0 " ); |
| aLine.append( (sal_Int32)aSize.Height() ); |
| aLine.append( " 0 0 ]\n" |
| "/Function " ); |
| aLine.append( nFunctionObject ); |
| aLine.append( " 0 R\n" |
| ">>\n" |
| "endobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| return true; |
| } |
| |
| bool PDFWriterImpl::writeJPG( JPGEmit& rObject ) |
| { |
| CHECK_RETURN( rObject.m_pStream ); |
| CHECK_RETURN( updateObject( rObject.m_nObject ) ); |
| |
| sal_Int32 nLength = 0; |
| rObject.m_pStream->Seek( STREAM_SEEK_TO_END ); |
| nLength = rObject.m_pStream->Tell(); |
| rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN ); |
| |
| sal_Int32 nMaskObject = 0; |
| if( !!rObject.m_aMask ) |
| { |
| if( rObject.m_aMask.GetBitCount() == 1 || |
| ( rObject.m_aMask.GetBitCount() == 8 && m_aContext.Version >= PDFWriter::PDF_1_4 && !m_bIsPDF_A1 )//i59651 |
| ) |
| { |
| nMaskObject = createObject(); |
| } |
| else if( m_bIsPDF_A1 ) |
| m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); |
| else if( m_aContext.Version < PDFWriter::PDF_1_4 ) |
| m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 ); |
| |
| } |
| #if OSL_DEBUG_LEVEL > 1 |
| emitComment( "PDFWriterImpl::writeJPG" ); |
| #endif |
| |
| OStringBuffer aLine(200); |
| aLine.append( rObject.m_nObject ); |
| aLine.append( " 0 obj\n" |
| "<</Type/XObject/Subtype/Image/Width " ); |
| aLine.append( (sal_Int32)rObject.m_aID.m_aPixelSize.Width() ); |
| aLine.append( " /Height " ); |
| aLine.append( (sal_Int32)rObject.m_aID.m_aPixelSize.Height() ); |
| aLine.append( " /BitsPerComponent 8 " ); |
| if( rObject.m_bTrueColor ) |
| aLine.append( "/ColorSpace/DeviceRGB" ); |
| else |
| aLine.append( "/ColorSpace/DeviceGray" ); |
| aLine.append( "/Filter/DCTDecode/Length " ); |
| aLine.append( nLength ); |
| if( nMaskObject ) |
| { |
| aLine.append( rObject.m_aMask.GetBitCount() == 1 ? " /Mask " : " /SMask " ); |
| aLine.append( nMaskObject ); |
| aLine.append( " 0 R " ); |
| } |
| aLine.append( ">>\nstream\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| checkAndEnableStreamEncryption( rObject.m_nObject ); |
| CHECK_RETURN( writeBuffer( rObject.m_pStream->GetData(), nLength ) ); |
| disableStreamEncryption(); |
| |
| aLine.setLength( 0 ); |
| aLine.append( "\nendstream\nendobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| if( nMaskObject ) |
| { |
| BitmapEmit aEmit; |
| aEmit.m_nObject = nMaskObject; |
| if( rObject.m_aMask.GetBitCount() == 1 ) |
| aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, rObject.m_aMask ); |
| else if( rObject.m_aMask.GetBitCount() == 8 ) |
| aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, AlphaMask( rObject.m_aMask ) ); |
| writeBitmapObject( aEmit, true ); |
| } |
| |
| return true; |
| } |
| |
| bool PDFWriterImpl::writeBitmapObject( BitmapEmit& rObject, bool bMask ) |
| { |
| CHECK_RETURN( updateObject( rObject.m_nObject ) ); |
| |
| Bitmap aBitmap; |
| Color aTransparentColor( COL_TRANSPARENT ); |
| bool bWriteMask = false; |
| if( ! bMask ) |
| { |
| aBitmap = rObject.m_aBitmap.GetBitmap(); |
| if( rObject.m_aBitmap.IsAlpha() ) |
| { |
| if( m_aContext.Version >= PDFWriter::PDF_1_4 ) |
| bWriteMask = true; |
| // else draw without alpha channel |
| } |
| else |
| { |
| switch( rObject.m_aBitmap.GetTransparentType() ) |
| { |
| case TRANSPARENT_NONE: |
| // comes from drawMask function |
| if( aBitmap.GetBitCount() == 1 && rObject.m_bDrawMask ) |
| bMask = true; |
| break; |
| case TRANSPARENT_COLOR: |
| aTransparentColor = rObject.m_aBitmap.GetTransparentColor(); |
| break; |
| case TRANSPARENT_BITMAP: |
| bWriteMask = true; |
| break; |
| } |
| } |
| } |
| else |
| { |
| if( m_aContext.Version < PDFWriter::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() ) |
| { |
| aBitmap = rObject.m_aBitmap.GetMask(); |
| aBitmap.Convert( BMP_CONVERSION_1BIT_THRESHOLD ); |
| DBG_ASSERT( aBitmap.GetBitCount() == 1, "mask conversion failed" ); |
| } |
| else if( aBitmap.GetBitCount() != 8 ) |
| { |
| aBitmap = rObject.m_aBitmap.GetAlpha().GetBitmap(); |
| aBitmap.Convert( BMP_CONVERSION_8BIT_GREYS ); |
| DBG_ASSERT( aBitmap.GetBitCount() == 8, "alpha mask conversion failed" ); |
| } |
| } |
| |
| BitmapReadAccess* pAccess = aBitmap.AcquireReadAccess(); |
| AccessReleaser aReleaser( pAccess ); |
| |
| bool bTrueColor; |
| sal_Int32 nBitsPerComponent; |
| switch( aBitmap.GetBitCount() ) |
| { |
| case 1: |
| case 2: |
| case 4: |
| case 8: |
| bTrueColor = false; |
| nBitsPerComponent = aBitmap.GetBitCount(); |
| break; |
| default: |
| bTrueColor = true; |
| nBitsPerComponent = 8; |
| break; |
| } |
| |
| sal_Int32 nStreamLengthObject = createObject(); |
| sal_Int32 nMaskObject = 0; |
| |
| #if OSL_DEBUG_LEVEL > 1 |
| emitComment( "PDFWriterImpl::writeBitmapObject" ); |
| #endif |
| OStringBuffer aLine(1024); |
| aLine.append( rObject.m_nObject ); |
| aLine.append( " 0 obj\n" |
| "<</Type/XObject/Subtype/Image/Width " ); |
| aLine.append( (sal_Int32)aBitmap.GetSizePixel().Width() ); |
| aLine.append( "/Height " ); |
| aLine.append( (sal_Int32)aBitmap.GetSizePixel().Height() ); |
| aLine.append( "/BitsPerComponent " ); |
| aLine.append( nBitsPerComponent ); |
| aLine.append( "/Length " ); |
| aLine.append( nStreamLengthObject ); |
| aLine.append( " 0 R\n" ); |
| #ifndef DEBUG_DISABLE_PDFCOMPRESSION |
| if( nBitsPerComponent != 1 ) |
| { |
| aLine.append( "/Filter/FlateDecode" ); |
| } |
| else |
| { |
| aLine.append( "/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns " ); |
| aLine.append( (sal_Int32)aBitmap.GetSizePixel().Width() ); |
| aLine.append( ">>\n" ); |
| } |
| #endif |
| if( ! bMask ) |
| { |
| aLine.append( "/ColorSpace" ); |
| if( bTrueColor ) |
| aLine.append( "/DeviceRGB\n" ); |
| else if( aBitmap.HasGreyPalette() ) |
| { |
| aLine.append( "/DeviceGray\n" ); |
| if( aBitmap.GetBitCount() == 1 ) |
| { |
| // #i47395# 1 bit bitmaps occasionally have an inverted grey palette |
| sal_Int32 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( Color( COL_BLACK ) ) ); |
| DBG_ASSERT( nBlackIndex == 0 || nBlackIndex == 1, "wrong black index" ); |
| if( nBlackIndex == 1 ) |
| aLine.append( "/Decode[1 0]\n" ); |
| } |
| } |
| else |
| { |
| aLine.append( "[ /Indexed/DeviceRGB " ); |
| aLine.append( (sal_Int32)(pAccess->GetPaletteEntryCount()-1) ); |
| aLine.append( "\n<" ); |
| if( m_aContext.Encryption.Encrypt() ) |
| { |
| enableStringEncryption( rObject.m_nObject ); |
| //check encryption buffer size |
| if( checkEncryptionBufferSize( pAccess->GetPaletteEntryCount()*3 ) ) |
| { |
| int nChar = 0; |
| //fill the encryption buffer |
| for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ ) |
| { |
| const BitmapColor& rColor = pAccess->GetPaletteColor( i ); |
| m_pEncryptionBuffer[nChar++] = rColor.GetRed(); |
| m_pEncryptionBuffer[nChar++] = rColor.GetGreen(); |
| m_pEncryptionBuffer[nChar++] = rColor.GetBlue(); |
| } |
| //encrypt the colorspace lookup table |
| rtl_cipher_encodeARCFOUR( m_aCipher, m_pEncryptionBuffer, nChar, m_pEncryptionBuffer, nChar ); |
| //now queue the data for output |
| nChar = 0; |
| for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ ) |
| { |
| appendHex(m_pEncryptionBuffer[nChar++], aLine ); |
| appendHex(m_pEncryptionBuffer[nChar++], aLine ); |
| appendHex(m_pEncryptionBuffer[nChar++], aLine ); |
| } |
| } |
| } |
| else //no encryption requested (PDF/A-1a program flow drops here) |
| { |
| for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ ) |
| { |
| const BitmapColor& rColor = pAccess->GetPaletteColor( i ); |
| appendHex( rColor.GetRed(), aLine ); |
| appendHex( rColor.GetGreen(), aLine ); |
| appendHex( rColor.GetBlue(), aLine ); |
| } |
| } |
| aLine.append( ">\n]\n" ); |
| } |
| } |
| else |
| { |
| if( aBitmap.GetBitCount() == 1 ) |
| { |
| aLine.append( "/ImageMask true\n" ); |
| sal_Int32 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( Color( COL_BLACK ) ) ); |
| DBG_ASSERT( nBlackIndex == 0 || nBlackIndex == 1, "wrong black index" ); |
| if( nBlackIndex ) |
| aLine.append( "/Decode[ 1 0 ]\n" ); |
| else |
| aLine.append( "/Decode[ 0 1 ]\n" ); |
| } |
| else if( aBitmap.GetBitCount() == 8 ) |
| { |
| aLine.append( "/ColorSpace/DeviceGray\n" |
| "/Decode [ 1 0 ]\n" ); |
| } |
| } |
| |
| if( ! bMask && m_aContext.Version > PDFWriter::PDF_1_2 && !m_bIsPDF_A1 )//i59651 |
| { |
| if( bWriteMask ) |
| { |
| nMaskObject = createObject(); |
| if( rObject.m_aBitmap.IsAlpha() && m_aContext.Version > PDFWriter::PDF_1_3 ) |
| aLine.append( "/SMask " ); |
| else |
| aLine.append( "/Mask " ); |
| aLine.append( nMaskObject ); |
| aLine.append( " 0 R\n" ); |
| } |
| else if( aTransparentColor != Color( COL_TRANSPARENT ) ) |
| { |
| aLine.append( "/Mask[ " ); |
| if( bTrueColor ) |
| { |
| aLine.append( (sal_Int32)aTransparentColor.GetRed() ); |
| aLine.append( ' ' ); |
| aLine.append( (sal_Int32)aTransparentColor.GetRed() ); |
| aLine.append( ' ' ); |
| aLine.append( (sal_Int32)aTransparentColor.GetGreen() ); |
| aLine.append( ' ' ); |
| aLine.append( (sal_Int32)aTransparentColor.GetGreen() ); |
| aLine.append( ' ' ); |
| aLine.append( (sal_Int32)aTransparentColor.GetBlue() ); |
| aLine.append( ' ' ); |
| aLine.append( (sal_Int32)aTransparentColor.GetBlue() ); |
| } |
| else |
| { |
| sal_Int32 nIndex = pAccess->GetBestPaletteIndex( BitmapColor( aTransparentColor ) ); |
| aLine.append( nIndex ); |
| } |
| aLine.append( " ]\n" ); |
| } |
| } |
| else if( m_bIsPDF_A1 && (bWriteMask || aTransparentColor != Color( COL_TRANSPARENT )) ) |
| m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); |
| |
| aLine.append( ">>\n" |
| "stream\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| sal_uInt64 nStartPos = 0; |
| CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nStartPos )) ); |
| |
| checkAndEnableStreamEncryption( rObject.m_nObject ); |
| #ifndef DEBUG_DISABLE_PDFCOMPRESSION |
| if( nBitsPerComponent == 1 ) |
| { |
| writeG4Stream( pAccess ); |
| } |
| else |
| #endif |
| { |
| beginCompression(); |
| if( ! bTrueColor || pAccess->GetScanlineFormat() == BMP_FORMAT_24BIT_TC_RGB ) |
| { |
| const int nScanLineBytes = 1 + ( pAccess->GetBitCount() * ( pAccess->Width() - 1 ) / 8U ); |
| |
| for( int i = 0; i < pAccess->Height(); i++ ) |
| { |
| CHECK_RETURN( writeBuffer( pAccess->GetScanline( i ), nScanLineBytes ) ); |
| } |
| } |
| else |
| { |
| const int nScanLineBytes = pAccess->Width()*3; |
| boost::shared_array<sal_uInt8> pCol( new sal_uInt8[ nScanLineBytes ] ); |
| for( int y = 0; y < pAccess->Height(); y++ ) |
| { |
| for( int x = 0; x < pAccess->Width(); x++ ) |
| { |
| BitmapColor aColor = pAccess->GetColor( y, x ); |
| pCol[3*x+0] = aColor.GetRed(); |
| pCol[3*x+1] = aColor.GetGreen(); |
| pCol[3*x+2] = aColor.GetBlue(); |
| } |
| CHECK_RETURN( writeBuffer( pCol.get(), nScanLineBytes ) ); |
| } |
| } |
| endCompression(); |
| } |
| disableStreamEncryption(); |
| |
| sal_uInt64 nEndPos = 0; |
| CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nEndPos )) ); |
| aLine.setLength( 0 ); |
| aLine.append( "\nendstream\nendobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| CHECK_RETURN( updateObject( nStreamLengthObject ) ); |
| aLine.setLength( 0 ); |
| aLine.append( nStreamLengthObject ); |
| aLine.append( " 0 obj\n" ); |
| aLine.append( (sal_Int64)(nEndPos-nStartPos) ); |
| aLine.append( "\nendobj\n\n" ); |
| CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); |
| |
| if( nMaskObject ) |
| { |
| BitmapEmit aEmit; |
| aEmit.m_nObject = nMaskObject; |
| aEmit.m_aBitmap = rObject.m_aBitmap; |
| return writeBitmapObject( aEmit, true ); |
| } |
| |
| return true; |
| } |
| |
| void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const Rectangle& rTargetArea, const Bitmap& rMask ) |
| { |
| MARK( "drawJPGBitmap" ); |
| |
| OStringBuffer aLine( 80 ); |
| updateGraphicsState(); |
| |
| // #i40055# sanity check |
| if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) ) |
| return; |
| if( ! (rSizePixel.Width() && rSizePixel.Height()) ) |
| return; |
| |
| rDCTData.Seek( 0 ); |
| if( bIsTrueColor && m_aContext.ColorMode == PDFWriter::DrawGreyscale ) |
| { |
| // need to convert to grayscale; |
| // load stream to bitmap and draw the bitmap instead |
| Graphic aGraphic; |
| GraphicConverter::Import( rDCTData, aGraphic, CVT_JPG ); |
| Bitmap aBmp( aGraphic.GetBitmap() ); |
| if( !!rMask && rMask.GetSizePixel() == aBmp.GetSizePixel() ) |
| { |
| BitmapEx aBmpEx( aBmp, rMask ); |
| drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmpEx ); |
| } |
| else |
| drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmp ); |
| return; |
| } |
| |
| SvMemoryStream* pStream = new SvMemoryStream; |
| *pStream << rDCTData; |
| pStream->Seek( STREAM_SEEK_TO_END ); |
| |
| BitmapID aID; |
| aID.m_aPixelSize = rSizePixel; |
| aID.m_nSize = pStream->Tell(); |
| pStream->Seek( STREAM_SEEK_TO_BEGIN ); |
| aID.m_nChecksum = rtl_crc32( 0, pStream->GetData(), aID.m_nSize ); |
| if( ! rMask.IsEmpty() ) |
| aID.m_nMaskChecksum = rMask.GetChecksum(); |
| |
| std::list< JPGEmit >::const_iterator it; |
| for( it = m_aJPGs.begin(); it != m_aJPGs.end() && ! (aID == it->m_aID); ++it ) |
| ; |
| if( it == m_aJPGs.end() ) |
| { |
| m_aJPGs.push_front( JPGEmit() ); |
| JPGEmit& rEmit = m_aJPGs.front(); |
| rEmit.m_nObject = createObject(); |
| rEmit.m_aID = aID; |
| rEmit.m_pStream = pStream; |
| rEmit.m_bTrueColor = bIsTrueColor; |
| if( !! rMask && rMask.GetSizePixel() == rSizePixel ) |
| rEmit.m_aMask = rMask; |
| |
| it = m_aJPGs.begin(); |
| } |
| else |
| delete pStream; |
| |
| aLine.append( "q " ); |
| sal_Int32 nCheckWidth = 0; |
| m_aPages.back().appendMappedLength( (sal_Int32)rTargetArea.GetWidth(), aLine, false, &nCheckWidth ); |
| aLine.append( " 0 0 " ); |
| sal_Int32 nCheckHeight = 0; |
| m_aPages.back().appendMappedLength( (sal_Int32)rTargetArea.GetHeight(), aLine, true, &nCheckHeight ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine ); |
| aLine.append( " cm\n/Im" ); |
| aLine.append( it->m_nObject ); |
| aLine.append( " Do Q\n" ); |
| if( nCheckWidth == 0 || nCheckHeight == 0 ) |
| { |
| // #i97512# avoid invalid current matrix |
| aLine.setLength( 0 ); |
| aLine.append( "\n%jpeg image /Im" ); |
| aLine.append( it->m_nObject ); |
| aLine.append( " scaled to zero size, omitted\n" ); |
| } |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| |
| OStringBuffer aObjName( 16 ); |
| aObjName.append( "Im" ); |
| aObjName.append( it->m_nObject ); |
| pushResource( ResXObject, aObjName.makeStringAndClear(), it->m_nObject ); |
| |
| } |
| |
| void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor ) |
| { |
| OStringBuffer aLine( 80 ); |
| updateGraphicsState(); |
| |
| aLine.append( "q " ); |
| if( rFillColor != Color( COL_TRANSPARENT ) ) |
| { |
| appendNonStrokingColor( rFillColor, aLine ); |
| aLine.append( ' ' ); |
| } |
| sal_Int32 nCheckWidth = 0; |
| m_aPages.back().appendMappedLength( (sal_Int32)rDestSize.Width(), aLine, false, &nCheckWidth ); |
| aLine.append( " 0 0 " ); |
| sal_Int32 nCheckHeight = 0; |
| m_aPages.back().appendMappedLength( (sal_Int32)rDestSize.Height(), aLine, true, &nCheckHeight ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), aLine ); |
| aLine.append( " cm\n/Im" ); |
| aLine.append( rBitmap.m_nObject ); |
| aLine.append( " Do Q\n" ); |
| if( nCheckWidth == 0 || nCheckHeight == 0 ) |
| { |
| // #i97512# avoid invalid current matrix |
| aLine.setLength( 0 ); |
| aLine.append( "\n%bitmap image /Im" ); |
| aLine.append( rBitmap.m_nObject ); |
| aLine.append( " scaled to zero size, omitted\n" ); |
| } |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| const PDFWriterImpl::BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx& i_rBitmap, bool bDrawMask ) |
| { |
| BitmapEx aBitmap( i_rBitmap ); |
| if( m_aContext.ColorMode == PDFWriter::DrawGreyscale ) |
| { |
| BmpConversion eConv = BMP_CONVERSION_8BIT_GREYS; |
| int nDepth = aBitmap.GetBitmap().GetBitCount(); |
| if( nDepth <= 4 ) |
| eConv = BMP_CONVERSION_4BIT_GREYS; |
| if( nDepth > 1 ) |
| aBitmap.Convert( eConv ); |
| } |
| BitmapID aID; |
| aID.m_aPixelSize = aBitmap.GetSizePixel(); |
| aID.m_nSize = aBitmap.GetBitCount(); |
| aID.m_nChecksum = aBitmap.GetBitmap().GetChecksum(); |
| aID.m_nMaskChecksum = 0; |
| if( aBitmap.IsAlpha() ) |
| aID.m_nMaskChecksum = aBitmap.GetAlpha().GetChecksum(); |
| else |
| { |
| Bitmap aMask = aBitmap.GetMask(); |
| if( ! aMask.IsEmpty() ) |
| aID.m_nMaskChecksum = aMask.GetChecksum(); |
| } |
| std::list< BitmapEmit >::const_iterator it; |
| for( it = m_aBitmaps.begin(); it != m_aBitmaps.end(); ++it ) |
| { |
| if( aID == it->m_aID ) |
| break; |
| } |
| if( it == m_aBitmaps.end() ) |
| { |
| m_aBitmaps.push_front( BitmapEmit() ); |
| m_aBitmaps.front().m_aID = aID; |
| m_aBitmaps.front().m_aBitmap = aBitmap; |
| m_aBitmaps.front().m_nObject = createObject(); |
| m_aBitmaps.front().m_bDrawMask = bDrawMask; |
| it = m_aBitmaps.begin(); |
| } |
| |
| OStringBuffer aObjName( 16 ); |
| aObjName.append( "Im" ); |
| aObjName.append( it->m_nObject ); |
| pushResource( ResXObject, aObjName.makeStringAndClear(), it->m_nObject ); |
| |
| return *it; |
| } |
| |
| void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap ) |
| { |
| MARK( "drawBitmap (Bitmap)" ); |
| |
| // #i40055# sanity check |
| if( ! (rDestSize.Width() && rDestSize.Height()) ) |
| return; |
| |
| const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( rBitmap ) ); |
| drawBitmap( rDestPoint, rDestSize, rEmit, Color( COL_TRANSPARENT ) ); |
| } |
| |
| void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEx& rBitmap ) |
| { |
| MARK( "drawBitmap (BitmapEx)" ); |
| |
| // #i40055# sanity check |
| if( ! (rDestSize.Width() && rDestSize.Height()) ) |
| return; |
| |
| const BitmapEmit& rEmit = createBitmapEmit( rBitmap ); |
| drawBitmap( rDestPoint, rDestSize, rEmit, Color( COL_TRANSPARENT ) ); |
| } |
| |
| void PDFWriterImpl::drawMask( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Color& rFillColor ) |
| { |
| MARK( "drawMask" ); |
| |
| // #i40055# sanity check |
| if( ! (rDestSize.Width() && rDestSize.Height()) ) |
| return; |
| |
| Bitmap aBitmap( rBitmap ); |
| if( aBitmap.GetBitCount() > 1 ) |
| aBitmap.Convert( BMP_CONVERSION_1BIT_THRESHOLD ); |
| DBG_ASSERT( aBitmap.GetBitCount() == 1, "mask conversion failed" ); |
| |
| const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( aBitmap ), true ); |
| drawBitmap( rDestPoint, rDestSize, rEmit, rFillColor ); |
| } |
| |
| sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize ) |
| { |
| Size aPtSize( lcl_convert( m_aGraphicsStack.front().m_aMapMode, |
| MapMode( MAP_POINT ), |
| getReferenceDevice(), |
| rSize ) ); |
| // check if we already have this gradient |
| std::list<GradientEmit>::iterator it; |
| // rounding to point will generally lose some pixels |
| // round up to point boundary |
| aPtSize.Width()++; |
| aPtSize.Height()++; |
| for( it = m_aGradients.begin(); it != m_aGradients.end(); ++it ) |
| { |
| if( it->m_aGradient == rGradient ) |
| { |
| if( it->m_aSize == aPtSize ) |
| break; |
| } |
| } |
| if( it == m_aGradients.end() ) |
| { |
| m_aGradients.push_front( GradientEmit() ); |
| m_aGradients.front().m_aGradient = rGradient; |
| m_aGradients.front().m_nObject = createObject(); |
| m_aGradients.front().m_aSize = aPtSize; |
| it = m_aGradients.begin(); |
| } |
| |
| OStringBuffer aObjName( 16 ); |
| aObjName.append( 'P' ); |
| aObjName.append( it->m_nObject ); |
| pushResource( ResShading, aObjName.makeStringAndClear(), it->m_nObject ); |
| |
| return it->m_nObject; |
| } |
| |
| void PDFWriterImpl::drawGradient( const Rectangle& rRect, const Gradient& rGradient ) |
| { |
| MARK( "drawGradient (Rectangle)" ); |
| |
| if( m_aContext.Version == PDFWriter::PDF_1_2 ) |
| { |
| drawRectangle( rRect ); |
| return; |
| } |
| |
| sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() ); |
| |
| Point aTranslate( rRect.BottomLeft() ); |
| aTranslate += Point( 0, 1 ); |
| |
| updateGraphicsState(); |
| |
| OStringBuffer aLine( 80 ); |
| aLine.append( "q 1 0 0 1 " ); |
| m_aPages.back().appendPoint( aTranslate, aLine ); |
| aLine.append( " cm " ); |
| // if a stroke is appended reset the clip region before stroke |
| if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) |
| aLine.append( "q " ); |
| aLine.append( "0 0 " ); |
| m_aPages.back().appendMappedLength( (sal_Int32)rRect.GetWidth(), aLine, false ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( (sal_Int32)rRect.GetHeight(), aLine, true ); |
| aLine.append( " re W n\n" ); |
| |
| aLine.append( "/P" ); |
| aLine.append( nGradient ); |
| aLine.append( " sh " ); |
| if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) |
| { |
| aLine.append( "Q 0 0 " ); |
| m_aPages.back().appendMappedLength( (sal_Int32)rRect.GetWidth(), aLine, false ); |
| aLine.append( ' ' ); |
| m_aPages.back().appendMappedLength( (sal_Int32)rRect.GetHeight(), aLine, true ); |
| aLine.append( " re S " ); |
| } |
| aLine.append( "Q\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| void PDFWriterImpl::drawGradient( const PolyPolygon& rPolyPoly, const Gradient& rGradient ) |
| { |
| MARK( "drawGradient (PolyPolygon)" ); |
| |
| if( m_aContext.Version == PDFWriter::PDF_1_2 ) |
| { |
| drawPolyPolygon( rPolyPoly ); |
| return; |
| } |
| |
| Rectangle aBoundRect = rPolyPoly.GetBoundRect(); |
| sal_Int32 nGradient = createGradient( rGradient, aBoundRect.GetSize() ); |
| |
| updateGraphicsState(); |
| |
| Point aTranslate = aBoundRect.BottomLeft(); |
| int nPolygons = rPolyPoly.Count(); |
| |
| OStringBuffer aLine( 80*nPolygons ); |
| aLine.append( "q " ); |
| // set PolyPolygon as clip path |
| m_aPages.back().appendPolyPolygon( rPolyPoly, aLine ); |
| aLine.append( "W* n\n" ); |
| aLine.append( "1 0 0 1 " ); |
| m_aPages.back().appendPoint( aTranslate, aLine ); |
| aLine.append( " cm\n" ); |
| aLine.append( "/P" ); |
| aLine.append( nGradient ); |
| aLine.append( " sh Q\n" ); |
| if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) |
| { |
| // and draw the surrounding path |
| m_aPages.back().appendPolyPolygon( rPolyPoly, aLine ); |
| aLine.append( "S\n" ); |
| } |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| void PDFWriterImpl::drawHatch( const PolyPolygon& rPolyPoly, const Hatch& rHatch ) |
| { |
| MARK( "drawHatch" ); |
| |
| updateGraphicsState(); |
| |
| if( rPolyPoly.Count() ) |
| { |
| PolyPolygon aPolyPoly( rPolyPoly ); |
| |
| aPolyPoly.Optimize( POLY_OPTIMIZE_NO_SAME ); |
| push( PUSH_LINECOLOR ); |
| setLineColor( rHatch.GetColor() ); |
| getReferenceDevice()->ImplDrawHatch( aPolyPoly, rHatch, sal_False ); |
| pop(); |
| } |
| } |
| |
| void PDFWriterImpl::drawWallpaper( const Rectangle& rRect, const Wallpaper& rWall ) |
| { |
| MARK( "drawWallpaper" ); |
| |
| bool bDrawColor = false; |
| bool bDrawGradient = false; |
| bool bDrawBitmap = false; |
| |
| BitmapEx aBitmap; |
| Point aBmpPos = rRect.TopLeft(); |
| Size aBmpSize; |
| if( rWall.IsBitmap() ) |
| { |
| aBitmap = rWall.GetBitmap(); |
| aBmpSize = lcl_convert( aBitmap.GetPrefMapMode(), |
| getMapMode(), |
| getReferenceDevice(), |
| aBitmap.GetPrefSize() ); |
| Rectangle aRect( rRect ); |
| if( rWall.IsRect() ) |
| { |
| aRect = rWall.GetRect(); |
| aBmpPos = aRect.TopLeft(); |
| aBmpSize = aRect.GetSize(); |
| } |
| if( rWall.GetStyle() != WALLPAPER_SCALE ) |
| { |
| if( rWall.GetStyle() != WALLPAPER_TILE ) |
| { |
| bDrawBitmap = true; |
| if( rWall.IsGradient() ) |
| bDrawGradient = true; |
| else |
| bDrawColor = true; |
| switch( rWall.GetStyle() ) |
| { |
| case WALLPAPER_TOPLEFT: |
| break; |
| case WALLPAPER_TOP: |
| aBmpPos.X() += (aRect.GetWidth()-aBmpSize.Width())/2; |
| break; |
| case WALLPAPER_LEFT: |
| aBmpPos.Y() += (aRect.GetHeight()-aBmpSize.Height())/2; |
| break; |
| case WALLPAPER_TOPRIGHT: |
| aBmpPos.X() += aRect.GetWidth()-aBmpSize.Width(); |
| break; |
| case WALLPAPER_CENTER: |
| aBmpPos.X() += (aRect.GetWidth()-aBmpSize.Width())/2; |
| aBmpPos.Y() += (aRect.GetHeight()-aBmpSize.Height())/2; |
| break; |
| case WALLPAPER_RIGHT: |
| aBmpPos.X() += aRect.GetWidth()-aBmpSize.Width(); |
| aBmpPos.Y() += (aRect.GetHeight()-aBmpSize.Height())/2; |
| break; |
| case WALLPAPER_BOTTOMLEFT: |
| aBmpPos.Y() += aRect.GetHeight()-aBmpSize.Height(); |
| break; |
| case WALLPAPER_BOTTOM: |
| aBmpPos.X() += (aRect.GetWidth()-aBmpSize.Width())/2; |
| aBmpPos.Y() += aRect.GetHeight()-aBmpSize.Height(); |
| break; |
| case WALLPAPER_BOTTOMRIGHT: |
| aBmpPos.X() += aRect.GetWidth()-aBmpSize.Width(); |
| aBmpPos.Y() += aRect.GetHeight()-aBmpSize.Height(); |
| break; |
| default: ; |
| } |
| } |
| else |
| { |
| // push the bitmap |
| const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( aBitmap ) ); |
| |
| // convert to page coordinates; this needs to be done here |
| // since the emit does not know the page anymore |
| Rectangle aConvertRect( aBmpPos, aBmpSize ); |
| m_aPages.back().convertRect( aConvertRect ); |
| |
| OStringBuffer aNameBuf(16); |
| aNameBuf.append( "Im" ); |
| aNameBuf.append( rEmit.m_nObject ); |
| OString aImageName( aNameBuf.makeStringAndClear() ); |
| |
| // push the pattern |
| OStringBuffer aTilingStream( 32 ); |
| appendFixedInt( aConvertRect.GetWidth(), aTilingStream ); |
| aTilingStream.append( " 0 0 " ); |
| appendFixedInt( aConvertRect.GetHeight(), aTilingStream ); |
| aTilingStream.append( " 0 0 cm\n/" ); |
| aTilingStream.append( aImageName ); |
| aTilingStream.append( " Do\n" ); |
| |
| m_aTilings.push_back( TilingEmit() ); |
| m_aTilings.back().m_nObject = createObject(); |
| m_aTilings.back().m_aRectangle = Rectangle( Point( 0, 0 ), aConvertRect.GetSize() ); |
| m_aTilings.back().m_pTilingStream = new SvMemoryStream(); |
| m_aTilings.back().m_pTilingStream->Write( aTilingStream.getStr(), aTilingStream.getLength() ); |
| // phase the tiling so wallpaper begins on upper left |
| m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor; |
| m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor; |
| m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject; |
| |
| updateGraphicsState(); |
| |
| OStringBuffer aObjName( 16 ); |
| aObjName.append( 'P' ); |
| aObjName.append( m_aTilings.back().m_nObject ); |
| OString aPatternName( aObjName.makeStringAndClear() ); |
| pushResource( ResPattern, aPatternName, m_aTilings.back().m_nObject ); |
| |
| // fill a rRect with the pattern |
| OStringBuffer aLine( 100 ); |
| aLine.append( "q /Pattern cs /" ); |
| aLine.append( aPatternName ); |
| aLine.append( " scn " ); |
| m_aPages.back().appendRect( rRect, aLine ); |
| aLine.append( " f Q\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| } |
| else |
| { |
| aBmpPos = aRect.TopLeft(); |
| aBmpSize = aRect.GetSize(); |
| bDrawBitmap = true; |
| } |
| |
| if( aBitmap.IsTransparent() ) |
| { |
| if( rWall.IsGradient() ) |
| bDrawGradient = true; |
| else |
| bDrawColor = true; |
| } |
| } |
| else if( rWall.IsGradient() ) |
| bDrawGradient = true; |
| else |
| bDrawColor = true; |
| |
| if( bDrawGradient ) |
| { |
| drawGradient( rRect, rWall.GetGradient() ); |
| } |
| if( bDrawColor ) |
| { |
| Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor; |
| Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor; |
| setLineColor( Color( COL_TRANSPARENT ) ); |
| setFillColor( rWall.GetColor() ); |
| drawRectangle( rRect ); |
| setLineColor( aOldLineColor ); |
| setFillColor( aOldFillColor ); |
| } |
| if( bDrawBitmap ) |
| { |
| // set temporary clip region since aBmpPos and aBmpSize |
| // may be outside rRect |
| OStringBuffer aLine( 20 ); |
| aLine.append( "q " ); |
| m_aPages.back().appendRect( rRect, aLine ); |
| aLine.append( " W n\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| drawBitmap( aBmpPos, aBmpSize, aBitmap ); |
| writeBuffer( "Q\n", 2 ); |
| } |
| } |
| |
| void PDFWriterImpl::beginPattern( const Rectangle& rCellRect ) |
| { |
| beginRedirect( new SvMemoryStream(), rCellRect ); |
| } |
| |
| sal_Int32 PDFWriterImpl::endPattern( const SvtGraphicFill::Transform& rTransform ) |
| { |
| Rectangle aConvertRect( getRedirectTargetRect() ); |
| DBG_ASSERT( aConvertRect.GetWidth() != 0 && aConvertRect.GetHeight() != 0, "empty cell rectangle in pattern" ); |
| |
| // get scaling between current mapmode and PDF output |
| Size aScaling( lcl_convert( m_aGraphicsStack.front().m_aMapMode, m_aMapMode, getReferenceDevice(), Size( 10000, 10000 ) ) ); |
| double fSX = (double(aScaling.Width()) / 10000.0); |
| double fSY = (double(aScaling.Height()) / 10000.0); |
| |
| // transform translation part of matrix |
| Size aTranslation( (long)rTransform.matrix[2], (long)rTransform.matrix[5] ); |
| aTranslation = lcl_convert( m_aGraphicsStack.front().m_aMapMode, m_aMapMode, getReferenceDevice(), aTranslation ); |
| |
| sal_Int32 nTilingId = m_aTilings.size(); |
| m_aTilings.push_back( TilingEmit() ); |
| TilingEmit& rTile = m_aTilings.back(); |
| rTile.m_nObject = createObject(); |
| rTile.m_aResources = m_aOutputStreams.front().m_aResourceDict; |
| rTile.m_aTransform.matrix[0] = rTransform.matrix[0] * fSX; |
| rTile.m_aTransform.matrix[1] = rTransform.matrix[1] * fSY; |
| rTile.m_aTransform.matrix[2] = aTranslation.Width(); |
| rTile.m_aTransform.matrix[3] = rTransform.matrix[3] * fSX; |
| rTile.m_aTransform.matrix[4] = rTransform.matrix[4] * fSY; |
| rTile.m_aTransform.matrix[5] = -aTranslation.Height(); |
| // caution: endRedirect pops the stream, so do this last |
| rTile.m_pTilingStream = dynamic_cast<SvMemoryStream*>(endRedirect()); |
| // FIXME: bound rect will not work with rotated matrix |
| rTile.m_aRectangle = Rectangle( Point(0,0), aConvertRect.GetSize() ); |
| rTile.m_aCellSize = aConvertRect.GetSize(); |
| |
| OStringBuffer aObjName( 16 ); |
| aObjName.append( 'P' ); |
| aObjName.append( rTile.m_nObject ); |
| pushResource( ResPattern, aObjName.makeStringAndClear(), rTile.m_nObject ); |
| return nTilingId; |
| } |
| |
| void PDFWriterImpl::drawPolyPolygon( const PolyPolygon& rPolyPoly, sal_Int32 nPattern, bool bEOFill ) |
| { |
| if( nPattern < 0 || nPattern >= (sal_Int32)m_aTilings.size() ) |
| return; |
| |
| m_aPages.back().endStream(); |
| sal_Int32 nXObject = createObject(); |
| OStringBuffer aNameBuf( 16 ); |
| aNameBuf.append( "Pol" ); |
| aNameBuf.append( nXObject ); |
| OString aObjName( aNameBuf.makeStringAndClear() ); |
| Rectangle aObjRect; |
| if( updateObject( nXObject ) ) |
| { |
| // get bounding rect of object |
| PolyPolygon aSubDiv; |
| rPolyPoly.AdaptiveSubdivide( aSubDiv ); |
| aObjRect = aSubDiv.GetBoundRect(); |
| Rectangle aConvObjRect( aObjRect ); |
| m_aPages.back().convertRect( aConvObjRect ); |
| |
| // move polypolygon to bottom left of page |
| PolyPolygon aLocalPath( rPolyPoly ); |
| sal_Int32 nPgWd = getReferenceDevice()->ImplGetDPIX() * m_aPages.back().getWidth() / 72; |
| sal_Int32 nPgHt = getReferenceDevice()->ImplGetDPIY() * m_aPages.back().getHeight() / 72; |
| Size aLogicPgSz = getReferenceDevice()->PixelToLogic( Size( nPgWd, nPgHt ), m_aGraphicsStack.front().m_aMapMode ); |
| sal_Int32 nXOff = aObjRect.Left(); |
| sal_Int32 nYOff = aLogicPgSz.Height() - aObjRect.Bottom(); |
| aLocalPath.Move( -nXOff, nYOff ); |
| |
| // prepare XObject's content stream |
| OStringBuffer aStream( 512 ); |
| aStream.append( "/Pattern cs /P" ); |
| aStream.append( m_aTilings[ nPattern ].m_nObject ); |
| aStream.append( " scn\n" ); |
| m_aPages.back().appendPolyPolygon( aLocalPath, aStream ); |
| aStream.append( bEOFill ? "f*" : "f" ); |
| SvMemoryStream aMemStream( aStream.getLength() ); |
| aMemStream.Write( aStream.getStr(), aStream.getLength() ); |
| bool bDeflate = compressStream( &aMemStream ); |
| aMemStream.Seek( STREAM_SEEK_TO_END ); |
| sal_Int32 nStreamLen = (sal_Int32)aMemStream.Tell(); |
| aMemStream.Seek( STREAM_SEEK_TO_BEGIN ); |
| |
| // add new XObject to global resource dict |
| m_aGlobalResourceDict.m_aXObjects[ aObjName ] = nXObject; |
| |
| // write XObject |
| OStringBuffer aLine( 512 ); |
| aLine.append( nXObject ); |
| aLine.append( " 0 obj\n" |
| "<</Type/XObject/Subtype/Form/BBox[0 0 " ); |
| appendFixedInt( aConvObjRect.GetWidth(), aLine ); |
| aLine.append( ' ' ); |
| appendFixedInt( aConvObjRect.GetHeight(), aLine ); |
| aLine.append( "]/Length " ); |
| aLine.append( nStreamLen ); |
| if( bDeflate ) |
| aLine.append( "/Filter/FlateDecode" ); |
| aLine.append( ">>\n" |
| "stream\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| checkAndEnableStreamEncryption( nXObject ); |
| writeBuffer( aMemStream.GetData(), nStreamLen ); |
| disableStreamEncryption(); |
| writeBuffer( "\nendstream\nendobj\n\n", 19 ); |
| } |
| m_aPages.back().beginStream(); |
| OStringBuffer aLine( 80 ); |
| aLine.append( "q 1 0 0 1 " ); |
| m_aPages.back().appendPoint( aObjRect.BottomLeft(), aLine ); |
| aLine.append( " cm/" ); |
| aLine.append( aObjName ); |
| aLine.append( " Do Q\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| void PDFWriterImpl::updateGraphicsState() |
| { |
| OStringBuffer aLine( 256 ); |
| GraphicsState& rNewState = m_aGraphicsStack.front(); |
| // first set clip region since it might invalidate everything else |
| |
| if( (rNewState.m_nUpdateFlags & GraphicsState::updateClipRegion) ) |
| { |
| rNewState.m_nUpdateFlags &= ~GraphicsState::updateClipRegion; |
| |
| if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion || |
| ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) ) |
| { |
| if( m_aCurrentPDFState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion.count() ) |
| { |
| aLine.append( "Q " ); |
| // invalidate everything but the clip region |
| m_aCurrentPDFState = GraphicsState(); |
| rNewState.m_nUpdateFlags = sal::static_int_cast<sal_uInt16>(~GraphicsState::updateClipRegion); |
| } |
| if( rNewState.m_bClipRegion && rNewState.m_aClipRegion.count() ) |
| { |
| // clip region is always stored in private PDF mapmode |
| MapMode aNewMapMode = rNewState.m_aMapMode; |
| rNewState.m_aMapMode = m_aMapMode; |
| getReferenceDevice()->SetMapMode( rNewState.m_aMapMode ); |
| m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode; |
| |
| aLine.append( "q " ); |
| m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, aLine ); |
| aLine.append( "W* n\n" ); |
| rNewState.m_aMapMode = aNewMapMode; |
| getReferenceDevice()->SetMapMode( rNewState.m_aMapMode ); |
| m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode; |
| } |
| } |
| } |
| |
| if( (rNewState.m_nUpdateFlags & GraphicsState::updateMapMode) ) |
| { |
| rNewState.m_nUpdateFlags &= ~GraphicsState::updateMapMode; |
| getReferenceDevice()->SetMapMode( rNewState.m_aMapMode ); |
| } |
| |
| if( (rNewState.m_nUpdateFlags & GraphicsState::updateFont) ) |
| { |
| rNewState.m_nUpdateFlags &= ~GraphicsState::updateFont; |
| getReferenceDevice()->SetFont( rNewState.m_aFont ); |
| getReferenceDevice()->ImplNewFont(); |
| } |
| |
| if( (rNewState.m_nUpdateFlags & GraphicsState::updateLayoutMode) ) |
| { |
| rNewState.m_nUpdateFlags &= ~GraphicsState::updateLayoutMode; |
| getReferenceDevice()->SetLayoutMode( rNewState.m_nLayoutMode ); |
| } |
| |
| if( (rNewState.m_nUpdateFlags & GraphicsState::updateDigitLanguage) ) |
| { |
| rNewState.m_nUpdateFlags &= ~GraphicsState::updateDigitLanguage; |
| getReferenceDevice()->SetDigitLanguage( rNewState.m_aDigitLanguage ); |
| } |
| |
| if( (rNewState.m_nUpdateFlags & GraphicsState::updateLineColor) ) |
| { |
| rNewState.m_nUpdateFlags &= ~GraphicsState::updateLineColor; |
| if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor && |
| rNewState.m_aLineColor != Color( COL_TRANSPARENT ) ) |
| { |
| appendStrokingColor( rNewState.m_aLineColor, aLine ); |
| aLine.append( "\n" ); |
| } |
| } |
| |
| if( (rNewState.m_nUpdateFlags & GraphicsState::updateFillColor) ) |
| { |
| rNewState.m_nUpdateFlags &= ~GraphicsState::updateFillColor; |
| if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor && |
| rNewState.m_aFillColor != Color( COL_TRANSPARENT ) ) |
| { |
| appendNonStrokingColor( rNewState.m_aFillColor, aLine ); |
| aLine.append( "\n" ); |
| } |
| } |
| |
| if( (rNewState.m_nUpdateFlags & GraphicsState::updateTransparentPercent) ) |
| { |
| rNewState.m_nUpdateFlags &= ~GraphicsState::updateTransparentPercent; |
| if( m_aContext.Version >= PDFWriter::PDF_1_4 && m_aCurrentPDFState.m_nTransparentPercent != rNewState.m_nTransparentPercent ) |
| { |
| // TODO: switch extended graphicsstate |
| } |
| } |
| |
| // everything is up to date now |
| m_aCurrentPDFState = m_aGraphicsStack.front(); |
| if( aLine.getLength() ) |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| } |
| |
| /* #i47544# imitate OutputDevice behaviour: |
| * if a font with a nontransparent color is set, it overwrites the current |
| * text color. OTOH setting the text color will overwrite the color of the font. |
| */ |
| void PDFWriterImpl::setFont( const Font& rFont ) |
| { |
| Color aColor = rFont.GetColor(); |
| if( aColor == Color( COL_TRANSPARENT ) ) |
| aColor = m_aGraphicsStack.front().m_aFont.GetColor(); |
| m_aGraphicsStack.front().m_aFont = rFont; |
| m_aGraphicsStack.front().m_aFont.SetColor( aColor ); |
| m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsState::updateFont; |
| } |
| |
| void PDFWriterImpl::push( sal_uInt16 nFlags ) |
| { |
| OSL_ENSURE( m_aGraphicsStack.size() > 0, "invalid graphics stack" ); |
| m_aGraphicsStack.push_front( m_aGraphicsStack.front() ); |
| m_aGraphicsStack.front().m_nFlags = nFlags; |
| } |
| |
| void PDFWriterImpl::pop() |
| { |
| OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" ); |
| if( m_aGraphicsStack.size() < 2 ) |
| return; |
| |
| GraphicsState aState = m_aGraphicsStack.front(); |
| m_aGraphicsStack.pop_front(); |
| GraphicsState& rOld = m_aGraphicsStack.front(); |
| |
| // move those parameters back that were not pushed |
| // in the first place |
| if( ! (aState.m_nFlags & PUSH_LINECOLOR) ) |
| setLineColor( aState.m_aLineColor ); |
| if( ! (aState.m_nFlags & PUSH_FILLCOLOR) ) |
| setFillColor( aState.m_aFillColor ); |
| if( ! (aState.m_nFlags & PUSH_FONT) ) |
| setFont( aState.m_aFont ); |
| if( ! (aState.m_nFlags & PUSH_TEXTCOLOR) ) |
| setTextColor( aState.m_aFont.GetColor() ); |
| if( ! (aState.m_nFlags & PUSH_MAPMODE) ) |
| setMapMode( aState.m_aMapMode ); |
| if( ! (aState.m_nFlags & PUSH_CLIPREGION) ) |
| { |
| // do not use setClipRegion here |
| // it would convert again assuming the current mapmode |
| rOld.m_aClipRegion = aState.m_aClipRegion; |
| rOld.m_bClipRegion = aState.m_bClipRegion; |
| } |
| if( ! (aState.m_nFlags & PUSH_TEXTLINECOLOR ) ) |
| setTextLineColor( aState.m_aTextLineColor ); |
| if( ! (aState.m_nFlags & PUSH_OVERLINECOLOR ) ) |
| setOverlineColor( aState.m_aOverlineColor ); |
| if( ! (aState.m_nFlags & PUSH_TEXTALIGN ) ) |
| setTextAlign( aState.m_aFont.GetAlign() ); |
| if( ! (aState.m_nFlags & PUSH_TEXTFILLCOLOR) ) |
| setTextFillColor( aState.m_aFont.GetFillColor() ); |
| if( ! (aState.m_nFlags & PUSH_REFPOINT) ) |
| { |
| // what ? |
| } |
| // invalidate graphics state |
| m_aGraphicsStack.front().m_nUpdateFlags = sal::static_int_cast<sal_uInt16>(~0U); |
| } |
| |
| void PDFWriterImpl::setMapMode( const MapMode& rMapMode ) |
| { |
| m_aGraphicsStack.front().m_aMapMode = rMapMode; |
| getReferenceDevice()->SetMapMode( rMapMode ); |
| m_aCurrentPDFState.m_aMapMode = rMapMode; |
| } |
| |
| void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion ) |
| { |
| basegfx::B2DPolyPolygon aRegion = getReferenceDevice()->LogicToPixel( rRegion, m_aGraphicsStack.front().m_aMapMode ); |
| aRegion = getReferenceDevice()->PixelToLogic( aRegion, m_aMapMode ); |
| m_aGraphicsStack.front().m_aClipRegion = aRegion; |
| m_aGraphicsStack.front().m_bClipRegion = true; |
| m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsState::updateClipRegion; |
| } |
| |
| void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY ) |
| { |
| if( m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count() ) |
| { |
| Point aPoint( lcl_convert( m_aGraphicsStack.front().m_aMapMode, |
| m_aMapMode, |
| getReferenceDevice(), |
| Point( nX, nY ) ) ); |
| aPoint -= lcl_convert( m_aGraphicsStack.front().m_aMapMode, |
| m_aMapMode, |
| getReferenceDevice(), |
| Point() ); |
| basegfx::B2DHomMatrix aMat; |
| aMat.translate( aPoint.X(), aPoint.Y() ); |
| m_aGraphicsStack.front().m_aClipRegion.transform( aMat ); |
| m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsState::updateClipRegion; |
| } |
| } |
| |
| bool PDFWriterImpl::intersectClipRegion( const Rectangle& rRect ) |
| { |
| basegfx::B2DPolyPolygon aRect( basegfx::tools::createPolygonFromRect( |
| basegfx::B2DRectangle( rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom() ) ) ); |
| return intersectClipRegion( aRect ); |
| } |
| |
| |
| bool PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion ) |
| { |
| basegfx::B2DPolyPolygon aRegion( getReferenceDevice()->LogicToPixel( rRegion, m_aGraphicsStack.front().m_aMapMode ) ); |
| aRegion = getReferenceDevice()->PixelToLogic( aRegion, m_aMapMode ); |
| m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsState::updateClipRegion; |
| if( m_aGraphicsStack.front().m_bClipRegion ) |
| { |
| basegfx::B2DPolyPolygon aOld( basegfx::tools::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) ); |
| aRegion = basegfx::tools::prepareForPolygonOperation( aRegion ); |
| m_aGraphicsStack.front().m_aClipRegion = basegfx::tools::solvePolygonOperationAnd( aOld, aRegion ); |
| } |
| else |
| { |
| m_aGraphicsStack.front().m_aClipRegion = aRegion; |
| m_aGraphicsStack.front().m_bClipRegion = true; |
| } |
| return true; |
| } |
| |
| void PDFWriterImpl::createNote( const Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr ) |
| { |
| if( nPageNr < 0 ) |
| nPageNr = m_nCurrentPage; |
| |
| if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() ) |
| return; |
| |
| m_aNotes.push_back( PDFNoteEntry() ); |
| m_aNotes.back().m_nObject = createObject(); |
| m_aNotes.back().m_aContents = rNote; |
| m_aNotes.back().m_aRect = rRect; |
| // convert to default user space now, since the mapmode may change |
| m_aPages[nPageNr].convertRect( m_aNotes.back().m_aRect ); |
| |
| // insert note to page's annotation list |
| m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aNotes.back().m_nObject ); |
| } |
| |
| sal_Int32 PDFWriterImpl::createLink( const Rectangle& rRect, sal_Int32 nPageNr ) |
| { |
| if( nPageNr < 0 ) |
| nPageNr = m_nCurrentPage; |
| |
| if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() ) |
| return -1; |
| |
| sal_Int32 nRet = m_aLinks.size(); |
| |
| m_aLinks.push_back( PDFLink() ); |
| m_aLinks.back().m_nObject = createObject(); |
| m_aLinks.back().m_nPage = nPageNr; |
| m_aLinks.back().m_aRect = rRect; |
| // convert to default user space now, since the mapmode may change |
| m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect ); |
| |
| // insert link to page's annotation list |
| m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject ); |
| |
| return nRet; |
| } |
| |
| //--->i56629 |
| sal_Int32 PDFWriterImpl::createNamedDest( const rtl::OUString& sDestName, const Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) |
| { |
| if( nPageNr < 0 ) |
| nPageNr = m_nCurrentPage; |
| |
| if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() ) |
| return -1; |
| |
| sal_Int32 nRet = m_aNamedDests.size(); |
| |
| m_aNamedDests.push_back( PDFNamedDest() ); |
| m_aNamedDests.back().m_aDestName = sDestName; |
| m_aNamedDests.back().m_nPage = nPageNr; |
| m_aNamedDests.back().m_eType = eType; |
| m_aNamedDests.back().m_aRect = rRect; |
| // convert to default user space now, since the mapmode may change |
| m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect ); |
| |
| return nRet; |
| } |
| //<---i56629 |
| |
| sal_Int32 PDFWriterImpl::createDest( const Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) |
| { |
| if( nPageNr < 0 ) |
| nPageNr = m_nCurrentPage; |
| |
| if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() ) |
| return -1; |
| |
| sal_Int32 nRet = m_aDests.size(); |
| |
| m_aDests.push_back( PDFDest() ); |
| m_aDests.back().m_nPage = nPageNr; |
| m_aDests.back().m_eType = eType; |
| m_aDests.back().m_aRect = rRect; |
| // convert to default user space now, since the mapmode may change |
| m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect ); |
| |
| return nRet; |
| } |
| |
| sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) |
| { |
| return m_aDestinationIdTranslation[ nDestId ] = createDest( rRect, nPageNr, eType ); |
| } |
| |
| sal_Int32 PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId ) |
| { |
| if( nLinkId < 0 || nLinkId >= (sal_Int32)m_aLinks.size() ) |
| return -1; |
| if( nDestId < 0 || nDestId >= (sal_Int32)m_aDests.size() ) |
| return -2; |
| |
| m_aLinks[ nLinkId ].m_nDest = nDestId; |
| |
| return 0; |
| } |
| |
| sal_Int32 PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL ) |
| { |
| if( nLinkId < 0 || nLinkId >= (sal_Int32)m_aLinks.size() ) |
| return -1; |
| |
| m_aLinks[ nLinkId ].m_nDest = -1; |
| |
| using namespace ::com::sun::star; |
| |
| if (!m_xTrans.is()) |
| { |
| uno::Reference< lang::XMultiServiceFactory > xFact( comphelper::getProcessServiceFactory() ); |
| if( xFact.is() ) |
| { |
| m_xTrans = uno::Reference < util::XURLTransformer >( |
| xFact->createInstance( OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.util.URLTransformer" ) ) ), uno::UNO_QUERY ); |
| } |
| } |
| |
| util::URL aURL; |
| aURL.Complete = rURL; |
| |
| if (m_xTrans.is()) |
| m_xTrans->parseStrict( aURL ); |
| |
| m_aLinks[ nLinkId ].m_aURL = aURL.Complete; |
| |
| return 0; |
| } |
| |
| void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId ) |
| { |
| m_aLinkPropertyMap[ nPropertyId ] = nLinkId; |
| } |
| |
| sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, const OUString& rText, sal_Int32 nDestID ) |
| { |
| // create new item |
| sal_Int32 nNewItem = m_aOutline.size(); |
| m_aOutline.push_back( PDFOutlineEntry() ); |
| |
| // set item attributes |
| setOutlineItemParent( nNewItem, nParent ); |
| setOutlineItemText( nNewItem, rText ); |
| setOutlineItemDest( nNewItem, nDestID ); |
| |
| return nNewItem; |
| } |
| |
| sal_Int32 PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent ) |
| { |
| if( nItem < 1 || nItem >= (sal_Int32)m_aOutline.size() ) |
| return -1; |
| |
| int nRet = 0; |
| |
| if( nNewParent < 0 || nNewParent >= (sal_Int32)m_aOutline.size() || nNewParent == nItem ) |
| { |
| nNewParent = 0; |
| nRet = -2; |
| } |
| // remove item from previous parent |
| sal_Int32 nParentID = m_aOutline[ nItem ].m_nParentID; |
| if( nParentID >= 0 && nParentID < (sal_Int32)m_aOutline.size() ) |
| { |
| PDFOutlineEntry& rParent = m_aOutline[ nParentID ]; |
| |
| for( std::vector<sal_Int32>::iterator it = rParent.m_aChildren.begin(); |
| it != rParent.m_aChildren.end(); ++it ) |
| { |
| if( *it == nItem ) |
| { |
| rParent.m_aChildren.erase( it ); |
| break; |
| } |
| } |
| } |
| |
| // insert item to new parent's list of children |
| m_aOutline[ nNewParent ].m_aChildren.push_back( nItem ); |
| |
| return nRet; |
| } |
| |
| sal_Int32 PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, const OUString& rText ) |
| { |
| if( nItem < 1 || nItem >= (sal_Int32)m_aOutline.size() ) |
| return -1; |
| |
| m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText ); |
| return 0; |
| } |
| |
| sal_Int32 PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID ) |
| { |
| if( nItem < 1 || nItem >= (sal_Int32)m_aOutline.size() ) // item does not exist |
| return -1; |
| if( nDestID < 0 || nDestID >= (sal_Int32)m_aDests.size() ) // dest does not exist |
| return -2; |
| m_aOutline[nItem].m_nDestID = nDestID; |
| return 0; |
| } |
| |
| const sal_Char* PDFWriterImpl::getStructureTag( PDFWriter::StructElement eType ) |
| { |
| static std::map< PDFWriter::StructElement, const char* > aTagStrings; |
| if( aTagStrings.empty() ) |
| { |
| aTagStrings[ PDFWriter::NonStructElement] = "NonStruct"; |
| aTagStrings[ PDFWriter::Document ] = "Document"; |
| aTagStrings[ PDFWriter::Part ] = "Part"; |
| aTagStrings[ PDFWriter::Article ] = "Art"; |
| aTagStrings[ PDFWriter::Section ] = "Sect"; |
| aTagStrings[ PDFWriter::Division ] = "Div"; |
| aTagStrings[ PDFWriter::BlockQuote ] = "BlockQuote"; |
| aTagStrings[ PDFWriter::Caption ] = "Caption"; |
| aTagStrings[ PDFWriter::TOC ] = "TOC"; |
| aTagStrings[ PDFWriter::TOCI ] = "TOCI"; |
| aTagStrings[ PDFWriter::Index ] = "Index"; |
| aTagStrings[ PDFWriter::Paragraph ] = "P"; |
| aTagStrings[ PDFWriter::Heading ] = "H"; |
| aTagStrings[ PDFWriter::H1 ] = "H1"; |
| aTagStrings[ PDFWriter::H2 ] = "H2"; |
| aTagStrings[ PDFWriter::H3 ] = "H3"; |
| aTagStrings[ PDFWriter::H4 ] = "H4"; |
| aTagStrings[ PDFWriter::H5 ] = "H5"; |
| aTagStrings[ PDFWriter::H6 ] = "H6"; |
| aTagStrings[ PDFWriter::List ] = "L"; |
| aTagStrings[ PDFWriter::ListItem ] = "LI"; |
| aTagStrings[ PDFWriter::LILabel ] = "Lbl"; |
| aTagStrings[ PDFWriter::LIBody ] = "LBody"; |
| aTagStrings[ PDFWriter::Table ] = "Table"; |
| aTagStrings[ PDFWriter::TableRow ] = "TR"; |
| aTagStrings[ PDFWriter::TableHeader ] = "TH"; |
| aTagStrings[ PDFWriter::TableData ] = "TD"; |
| aTagStrings[ PDFWriter::Span ] = "Span"; |
| aTagStrings[ PDFWriter::Quote ] = "Quote"; |
| aTagStrings[ PDFWriter::Note ] = "Note"; |
| aTagStrings[ PDFWriter::Reference ] = "Reference"; |
| aTagStrings[ PDFWriter::BibEntry ] = "BibEntry"; |
| aTagStrings[ PDFWriter::Code ] = "Code"; |
| aTagStrings[ PDFWriter::Link ] = "Link"; |
| aTagStrings[ PDFWriter::Figure ] = "Figure"; |
| aTagStrings[ PDFWriter::Formula ] = "Formula"; |
| aTagStrings[ PDFWriter::Form ] = "Form"; |
| } |
| |
| std::map< PDFWriter::StructElement, const char* >::const_iterator it = aTagStrings.find( eType ); |
| |
| return it != aTagStrings.end() ? it->second : "Div"; |
| } |
| |
| void PDFWriterImpl::beginStructureElementMCSeq() |
| { |
| if( m_bEmitStructure && |
| m_nCurrentStructElement > 0 && // StructTreeRoot |
| ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence |
| ) |
| { |
| PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ]; |
| OStringBuffer aLine( 128 ); |
| sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size(); |
| aLine.append( "/" ); |
| if( rEle.m_aAlias.getLength() > 0 ) |
| aLine.append( rEle.m_aAlias ); |
| else |
| aLine.append( getStructureTag( rEle.m_eType ) ); |
| aLine.append( "<</MCID " ); |
| aLine.append( nMCID ); |
| aLine.append( ">>BDC\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| |
| // update the element's content list |
| #if OSL_DEBUG_LEVEL > 1 |
| fprintf( stderr, "beginning marked content id %" SAL_PRIdINT32 " on page object %" SAL_PRIdINT32 ", structure first page = %" SAL_PRIdINT32 "\n", |
| nMCID, |
| m_aPages[ m_nCurrentPage ].m_nPageObject, |
| rEle.m_nFirstPageObject ); |
| #endif |
| rEle.m_aKids.push_back( PDFStructureElementKid( nMCID, m_aPages[m_nCurrentPage].m_nPageObject ) ); |
| // update the page's mcid parent list |
| m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject ); |
| // mark element MC sequence as open |
| rEle.m_bOpenMCSeq = true; |
| } |
| // handle artifacts |
| else if( ! m_bEmitStructure && m_aContext.Tagged && |
| m_nCurrentStructElement > 0 && |
| m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement && |
| ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence |
| ) |
| { |
| OStringBuffer aLine( 128 ); |
| aLine.append( "/Artifact BMC\n" ); |
| writeBuffer( aLine.getStr(), aLine.getLength() ); |
| // mark element MC sequence as open |
| m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true; |
| } |
| } |
| |
| void PDFWriterImpl::endStructureElementMCSeq() |
| { |
| if( m_nCurrentStructElement > 0 && // StructTreeRoot |
| ( m_bEmitStructure || m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement ) && |
| m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // must have an opened MC sequence |
| ) |
| { |
| writeBuffer( "EMC\n", 4 ); |
| m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false; |
| } |
| } |
| |
| bool PDFWriterImpl::checkEmitStructure() |
| { |
| bool bEmit = false; |
| if( m_aContext.Tagged ) |
| { |
| bEmit = true; |
| sal_Int32 nEle = m_nCurrentStructElement; |
| while( nEle > 0 && nEle < sal_Int32(m_aStructure.size()) ) |
| { |
| if( m_aStructure[ nEle ].m_eType == PDFWriter::NonStructElement ) |
| { |
| bEmit = false; |
| break; |
| } |
| nEle = m_aStructure[ nEle ].m_nParentElement; |
| } |
| } |
| return bEmit; |
| } |
| |
| sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType, const rtl::OUString& rAlias ) |
| { |
| if( m_nCurrentPage < 0 ) |
| return -1; |
| |
| if( ! m_aContext.Tagged ) |
| return -1; |
| |
| // close eventual current MC sequence |
| endStructureElementMCSeq(); |
| |
| if( m_nCurrentStructElement == 0 && |
| eType != PDFWriter::Document && eType != PDFWriter::NonStructElement ) |
| { |
| // struct tree root hit, but not beginning document |
| // this might happen with setCurrentStructureElement |
| // silently insert structure into document again if one properly exists |
| if( ! m_aStructure[ 0 ].m_aChildren.empty() ) |
| { |
| PDFWriter::StructElement childType = PDFWriter::NonStructElement; |
| sal_Int32 nNewCurElement = 0; |
| const std::list< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren; |
| for( std::list< sal_Int32 >::const_iterator it = rRootChildren.begin(); |
| childType != PDFWriter::Document && it != rRootChildren.end(); ++it ) |
| { |
| nNewCurElement = *it; |
| childType = m_aStructure[ nNewCurElement ].m_eType; |
| } |
| if( childType == PDFWriter::Document ) |
| { |
| m_nCurrentStructElement = nNewCurElement; |
| DBG_ASSERT( 0, "Structure element inserted to StructTreeRoot that is not a document" ); |
| } |
| else { |
| DBG_ERROR( "document structure in disorder !" ); |
| } |
| } |
| else { |
| DBG_ERROR( "PDF document structure MUST be contained in a Document element" ); |
| } |
| } |
| |
| sal_Int32 nNewId = sal_Int32(m_aStructure.size()); |
| m_aStructure.push_back( PDFStructureElement() ); |
| PDFStructureElement& rEle = m_aStructure.back(); |
| rEle.m_eType = eType; |
| rEle.m_nOwnElement = nNewId; |
| rEle.m_nParentElement = m_nCurrentStructElement; |
| rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject; |
| m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId ); |
| m_nCurrentStructElement = nNewId; |
| |
| // handle alias names |
| if( rAlias.getLength() && eType != PDFWriter::NonStructElement ) |
| { |
| OStringBuffer aNameBuf( rAlias.getLength() ); |
| appendName( rAlias, aNameBuf ); |
| OString aAliasName( aNameBuf.makeStringAndClear() ); |
| rEle.m_aAlias = aAliasName; |
| m_aRoleMap[ aAliasName ] = getStructureTag( eType ); |
| } |
| |
| #if OSL_DEBUG_LEVEL > 1 |
| OStringBuffer aLine( "beginStructureElement " ); |
| aLine.append( m_nCurrentStructElement ); |
| aLine.append( ": " ); |
| aLine.append( getStructureTag( eType ) ); |
| if( rEle.m_aAlias.getLength() ) |
| { |
| aLine.append( " aliased as \"" ); |
| aLine.append( rEle.m_aAlias ); |
| aLine.append( '\"' ); |
| } |
| emitComment( aLine.getStr() ); |
| #endif |
| |
| // check whether to emit structure henceforth |
| m_bEmitStructure = checkEmitStructure(); |
| |
| if( m_bEmitStructure ) // don't create nonexistant objects |
| { |
| rEle.m_nObject = createObject(); |
| // update parent's kids list |
| m_aStructure[ rEle.m_nParentElement ].m_aKids.push_back( rEle.m_nObject ); |
| } |
| return nNewId; |
| } |
| |
| void PDFWriterImpl::endStructureElement() |
| { |
| if( m_nCurrentPage < 0 ) |
| return; |
| |
| if( ! m_aContext.Tagged ) |
| return; |
| |
| if( m_nCurrentStructElement == 0 ) |
| { |
| // hit the struct tree root, that means there is an endStructureElement |
| // without corresponding beginStructureElement |
| return; |
| } |
| |
| // end the marked content sequence |
| endStructureElementMCSeq(); |
| |
| #if OSL_DEBUG_LEVEL > 1 |
| OStringBuffer aLine( "endStructureElement " ); |
| aLine.append( m_nCurrentStructElement ); |
| aLine.append( ": " ); |
| aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) ); |
| if( m_aStructure[ m_nCurrentStructElement ].m_aAlias.getLength() ) |
| { |
| aLine.append( " aliased as \"" ); |
| aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias ); |
| aLine.append( '\"' ); |
| } |
| #endif |
| |
| // "end" the structure element, the parent becomes current element |
| m_nCurrentStructElement = m_aStructure[ m_nCurrentStructElement ].m_nParentElement; |
| |
| // check whether to emit structure henceforth |
| m_bEmitStructure = checkEmitStructure(); |
| |
| #if OSL_DEBUG_LEVEL > 1 |
| if( m_bEmitStructure ) |
| emitComment( aLine.getStr() ); |
| #endif |
| } |
| |
| //---> i94258 |
| /* |
| * This function adds an internal structure list container to overcome the 8191 elements array limitation |
| * in kids element emission. |
| * Recursive function |
| * |
| */ |
| void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle ) |
| { |
| if( rEle.m_eType == PDFWriter::NonStructElement && |
| rEle.m_nOwnElement != rEle.m_nParentElement ) |
| return; |
| |
| for( std::list< sal_Int32 >::const_iterator it = rEle.m_aChildren.begin(); it != rEle.m_aChildren.end(); ++it ) |
| { |
| if( *it > 0 && *it < sal_Int32(m_aStructure.size()) ) |
| { |
| PDFStructureElement& rChild = m_aStructure[ *it ]; |
| if( rChild.m_eType != PDFWriter::NonStructElement ) |
| { |
| //triggered when a child of the rEle element is found |
| if( rChild.m_nParentElement == rEle.m_nOwnElement ) |
| addInternalStructureContainer( rChild );//examine the child |
| else |
| { |
| DBG_ERROR( "PDFWriterImpl::addInternalStructureContainer: invalid child structure element" ); |
| #if OSL_DEBUG_LEVEL > 1 |
| fprintf( stderr, "PDFWriterImpl::addInternalStructureContainer: invalid child structure elemnt with id %" SAL_PRIdINT32 "\n", *it ); |
| #endif |
| } |
| } |
| } |
| else |
| { |
| DBG_ERROR( "PDFWriterImpl::emitStructure: invalid child structure id" ); |
| #if OSL_DEBUG_LEVEL > 1 |
| fprintf( stderr, "PDFWriterImpl::addInternalStructureContainer: invalid child structure id %" SAL_PRIdINT32 "\n", *it ); |
| #endif |
| } |
| } |
| |
| if( rEle.m_nOwnElement != rEle.m_nParentElement ) |
| { |
| if( !rEle.m_aKids.empty() ) |
| { |
| if( rEle.m_aKids.size() > ncMaxPDFArraySize ) { |
| //then we need to add the containers for the kids elements |
| // a list to be used for the new kid element |
| std::list< PDFStructureElementKid > aNewKids; |
| std::list< sal_Int32 > aNewChildren; |
| |
| // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?) |
| OStringBuffer aNameBuf( "Div" ); |
| OString aAliasName( aNameBuf.makeStringAndClear() ); |
| m_aRoleMap[ aAliasName ] = getStructureTag( PDFWriter::Division ); |
| |
| while( rEle.m_aKids.size() > ncMaxPDFArraySize ) |
| { |
| sal_Int32 nCurrentStructElement = rEle.m_nOwnElement; |
| sal_Int32 nNewId = sal_Int32(m_aStructure.size()); |
| m_aStructure.push_back( PDFStructureElement() ); |
| PDFStructureElement& rEleNew = m_aStructure.back(); |
| rEleNew.m_aAlias = aAliasName; |
| rEleNew.m_eType = PDFWriter::Division; // a new Div type container |
| rEleNew.m_nOwnElement = nNewId; |
| rEleNew.m_nParentElement = nCurrentStructElement; |
| //inherit the same page as the first child to be reparented |
| rEleNew.m_nFirstPageObject = m_aStructure[ rEle.m_aChildren.front() ].m_nFirstPageObject; |
| rEleNew.m_nObject = createObject();//assign a PDF object number |
| //add the object to the kid list of the parent |
| aNewKids.push_back( PDFStructureElementKid( rEleNew.m_nObject ) ); |
| aNewChildren.push_back( nNewId ); |
| |
| std::list< sal_Int32 >::iterator aChildEndIt( rEle.m_aChildren.begin() ); |
| std::list< PDFStructureElementKid >::iterator aKidEndIt( rEle.m_aKids.begin() ); |
| advance( aChildEndIt, ncMaxPDFArraySize ); |
| advance( aKidEndIt, ncMaxPDFArraySize ); |
| |
| rEleNew.m_aKids.splice( rEleNew.m_aKids.begin(), |
| rEle.m_aKids, |
| rEle.m_aKids.begin(), |
| aKidEndIt ); |
| rEleNew.m_aChildren.splice( rEleNew.m_aChildren.begin(), |
| rEle.m_aChildren, |
| rEle.m_aChildren.begin(), |
| aChildEndIt ); |
| // set the kid's new parent |
| for( std::list< sal_Int32 >::const_iterator it = rEleNew.m_aChildren.begin(); |
| it != rEleNew.m_aChildren.end(); ++it ) |
| { |
| m_aStructure[ *it ].m_nParentElement = nNewId; |
| } |
| } |
| //finally add the new kids resulting from the container added |
| rEle.m_aKids.insert( rEle.m_aKids.begin(), aNewKids.begin(), aNewKids.end() ); |
| rEle.m_aChildren.insert( rEle.m_aChildren.begin(), aNewChildren.begin(), aNewChildren.end() ); |
| } |
| } |
| } |
| } |
| //<--- i94258 |
| |
| bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle ) |
| { |
| bool bSuccess = false; |
| |
| if( m_aContext.Tagged && nEle >= 0 && nEle < sal_Int32(m_aStructure.size()) ) |
| { |
| // end eventual previous marked content sequence |
| endStructureElementMCSeq(); |
| |
| m_nCurrentStructElement = nEle; |
| m_bEmitStructure = checkEmitStructure(); |
| #if OSL_DEBUG_LEVEL > 1 |
| OStringBuffer aLine( "setCurrentStructureElement " ); |
| aLine.append( m_nCurrentStructElement ); |
| aLine.append( ": " ); |
| aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) ); |
| if( m_aStructure[ m_nCurrentStructElement ].m_aAlias.getLength() ) |
| { |
| aLine.append( " aliased as \"" ); |
| aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias ); |
| aLine.append( '\"' ); |
| } |
| if( ! m_bEmitStructure ) |
| aLine.append( " (inside NonStruct)" ); |
| emitComment( aLine.getStr() ); |
| #endif |
| bSuccess = true; |
| } |
| |
| return bSuccess; |
| } |
| |
| sal_Int32 PDFWriterImpl::getCurrentStructureElement() |
| { |
| return m_nCurrentStructElement; |
| } |
| |
| bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal ) |
| { |
| if( !m_aContext.Tagged ) |
| return false; |
| |
| bool bInsert = false; |
| if( m_nCurrentStructElement > 0 && m_bEmitStructure ) |
| { |
| PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType; |
| switch( eAttr ) |
| { |
| case PDFWriter::Placement: |
| if( eVal == PDFWriter::Block || |
| eVal == PDFWriter::Inline || |
| eVal == PDFWriter::Before || |
| eVal == PDFWriter::Start || |
| eVal == PDFWriter::End ) |
| bInsert = true; |
| break; |
| case PDFWriter::WritingMode: |
| if( eVal == PDFWriter::LrTb || |
| eVal == PDFWriter::RlTb || |
| eVal == PDFWriter::TbRl ) |
| { |
| bInsert = true; |
| } |
| break; |
| case PDFWriter::TextAlign: |
| if( eVal == PDFWriter::Start || |
| eVal == PDFWriter::Center || |
| eVal == PDFWriter::End || |
| eVal == PDFWriter::Justify ) |
| { |
| if( eType == PDFWriter::Paragraph || |
| eType == PDFWriter::Heading || |
| eType == PDFWriter::H1 || |
| eType == PDFWriter::H2 || |
| eType == PDFWriter::H3 || |
| eType == PDFWriter::H4 || |
| eType == PDFWriter::H5 || |
| eType == PDFWriter::H6 || |
| eType == PDFWriter::List || |
| eType == PDFWriter::ListItem || |
| eType == PDFWriter::LILabel || |
| eType == PDFWriter::LIBody || |
| eType == PDFWriter::Table || |
| eType == PDFWriter::TableRow || |
| eType == PDFWriter::TableHeader || |
| eType == PDFWriter::TableData ) |
| { |
| bInsert = true; |
| } |
| } |
| break; |
| case PDFWriter::Width: |
| case PDFWriter::Height: |
| if( eVal == PDFWriter::Auto ) |
| { |
| if( eType == PDFWriter::Figure || |
| eType == PDFWriter::Formula || |
| eType == PDFWriter::Form || |
| eType == PDFWriter::Table || |
| eType == PDFWriter::TableHeader || |
| eType == PDFWriter::TableData ) |
| { |
| bInsert = true; |
| } |
| } |
| break; |
| case PDFWriter::BlockAlign: |
| if( eVal == PDFWriter::Before || |
| eVal == PDFWriter::Middle || |
| eVal == PDFWriter::After || |
| eVal == PDFWriter::Justify ) |
| { |
| if( eType == PDFWriter::TableHeader || |
| eType == PDFWriter::TableData ) |
| { |
| bInsert = true; |
| } |
| } |
| break; |
| case PDFWriter::InlineAlign: |
| if( eVal == PDFWriter::Start || |
| eVal == PDFWriter::Center || |
| eVal == PDFWriter::End ) |
| { |
| if( eType == PDFWriter::TableHeader || |
| eType == PDFWriter::TableData ) |
| { |
| bInsert = true; |
| } |
| } |
| break; |
| case PDFWriter::LineHeight: |
| if( eVal == PDFWriter::Normal || |
| eVal == PDFWriter::Auto ) |
| { |
| // only for ILSE and BLSE |
| if( eType == PDFWriter::Paragraph || |
| eType == PDFWriter::Heading || |
| eType == PDFWriter::H1 || |
| eType == PDFWriter::H2 || |
| eType == PDFWriter::H3 || |
| eType == PDFWriter::H4 || |
| eType == PDFWriter::H5 || |
| eType == PDFWriter::H6 || |
| eType == PDFWriter::List || |
| eType == PDFWriter::ListItem || |
| eType == PDFWriter::LILabel || |
| eType == PDFWriter::LIBody || |
| eType == PDFWriter::Table || |
| eType == PDFWriter::TableRow || |
| eType == PDFWriter::TableHeader || |
| eType == PDFWriter::TableData || |
| eType == PDFWriter::Span || |
| eType == PDFWriter::Quote || |
| eType == PDFWriter::Note || |
| eType == PDFWriter::Reference || |
| eType == PDFWriter::BibEntry || |
| eType == PDFWriter::Code || |
| eType == PDFWriter::Link ) |
| { |
| bInsert = true; |
| } |
| } |
| break; |
| case PDFWriter::TextDecorationType: |
| if( eVal == PDFWriter::NONE || |
| eVal == PDFWriter::Underline || |
| eVal == PDFWriter::Overline || |
| eVal == PDFWriter::LineThrough ) |
| { |
| // only for ILSE and BLSE |
| if( eType == PDFWriter::Paragraph || |
| eType == PDFWriter::Heading || |
| eType == PDFWriter::H1 || |
| eType == PDFWriter::H2 || |
| eType == PDFWriter::H3 || |
| eType == PDFWriter::H4 || |
| eType == PDFWriter::H5 || |
| eType == PDFWriter::H6 || |
| eType == PDFWriter::List || |
| eType == PDFWriter::ListItem || |
| eType == PDFWriter::LILabel || |
| eType == PDFWriter::LIBody || |
| eType == PDFWriter::Table || |
| eType == PDFWriter::TableRow || |
| eType == PDFWriter::TableHeader || |
| eType == PDFWriter::TableData || |
| eType == PDFWriter::Span || |
| eType == PDFWriter::Quote || |
| eType == PDFWriter::Note || |
| eType == PDFWriter::Reference || |
| eType == PDFWriter::BibEntry || |
| eType == PDFWriter::Code || |
| eType == PDFWriter::Link ) |
| { |
| bInsert = true; |
| } |
| } |
| break; |
| case PDFWriter::ListNumbering: |
| if( eVal == PDFWriter::NONE || |
| eVal == PDFWriter::Disc || |
| eVal == PDFWriter::Circle || |
| eVal == PDFWriter::Square || |
| eVal == PDFWriter::Decimal || |
| eVal == PDFWriter::UpperRoman || |
| eVal == PDFWriter::LowerRoman || |
| eVal == PDFWriter::UpperAlpha || |
| eVal == PDFWriter::LowerAlpha ) |
| { |
| if( eType == PDFWriter::List ) |
| bInsert = true; |
| } |
| break; |
| default: break; |
| } |
| } |
| |
| if( bInsert ) |
| m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal ); |
| #if OSL_DEBUG_LEVEL > 1 |
| else if( m_nCurrentStructElement > 0 && m_bEmitStructure ) |
| fprintf( stderr, "rejecting setStructureAttribute( %s, %s ) on %s (%s) element\n", |
| getAttributeTag( eAttr ), |
| getAttributeValueTag( eVal ), |
| getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ), |
| m_aStructure[ m_nCurrentStructElement ].m_aAlias.getStr() |
| ); |
| #endif |
| |
| return bInsert; |
| } |
| |
| bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue ) |
| { |
| if( ! m_aContext.Tagged ) |
| return false; |
| |
| bool bInsert = false; |
| if( m_nCurrentStructElement > 0 && m_bEmitStructure ) |
| { |
| if( eAttr == PDFWriter::Language ) |
| { |
| m_aStructure[ m_nCurrentStructElement ].m_aLocale = MsLangId::convertLanguageToLocale( (LanguageType)nValue ); |
| return true; |
| } |
| |
| PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType; |
| switch( eAttr ) |
| { |
| case PDFWriter::SpaceBefore: |
| case PDFWriter::SpaceAfter: |
| case PDFWriter::StartIndent: |
| case PDFWriter::EndIndent: |
| // just for BLSE |
| if( eType == PDFWriter::Paragraph || |
| eType == PDFWriter::Heading || |
| eType == PDFWriter::H1 || |
| eType == PDFWriter::H2 || |
| eType == PDFWriter::H3 || |
| eType == PDFWriter::H4 || |
| eType == PDFWriter::H5 || |
| eType == PDFWriter::H6 || |
| eType == PDFWriter::List || |
| eType == PDFWriter::ListItem || |
| eType == PDFWriter::LILabel || |
| eType == PDFWriter::LIBody || |
| eType == PDFWriter::Table || |
| eType == PDFWriter::TableRow || |
| eType == PDFWriter::TableHeader || |
| eType == PDFWriter::TableData ) |
| { |
| bInsert = true; |
| } |
| break; |
| case PDFWriter::TextIndent: |
| // paragraph like BLSE and additional elements |
| if( eType == PDFWriter::Paragraph || |
| eType == PDFWriter::Heading || |
| eType == PDFWriter::H1 || |
| eType == PDFWriter::H2 || |
| eType == PDFWriter::H3 || |
| eType == PDFWriter::H4 || |
| eType == PDFWriter::H5 || |
| eType == PDFWriter::H6 || |
| eType == PDFWriter::LILabel || |
| eType == PDFWriter::LIBody || |
| eType == PDFWriter::TableHeader || |
| eType == PDFWriter::TableData ) |
| { |
| bInsert = true; |
| } |
| break; |
| case PDFWriter::Width: |
| case PDFWriter::Height: |
| if( eType == PDFWriter::Figure || |
| eType == PDFWriter::Formula || |
| eType == PDFWriter::Form || |
| eType == PDFWriter::Table || |
| eType == PDFWriter::TableHeader || |
| eType == PDFWriter::TableData ) |
| { |
| bInsert = true; |
| } |
| break; |
| case PDFWriter::LineHeight: |
| case PDFWriter::BaselineShift: |
| // only for ILSE and BLSE |
| if( eType == PDFWriter::Paragraph || |
| eType == PDFWriter::Heading || |
| eType == PDFWriter::H1 || |
| eType == PDFWriter::H2 || |
| eType == PDFWriter::H3 || |
| eType == PDFWriter::H4 || |
| eType == PDFWriter::H5 || |
| eType == PDFWriter::H6 || |
| eType == PDFWriter::List || |
| eType == PDFWriter::ListItem || |
| eType == PDFWriter::LILabel || |
| eType == PDFWriter::LIBody || |
| eType == PDFWriter::Table || |
| eType == PDFWriter::TableRow || |
| eType == PDFWriter::TableHeader || |
| eType == PDFWriter::TableData || |
| eType == PDFWriter::Span || |
| eType == PDFWriter::Quote || |
| eType == PDFWriter::Note || |
| eType == PDFWriter::Reference || |
| eType == PDFWriter::BibEntry || |
| eType == PDFWriter::Code || |
| eType == PDFWriter::Link ) |
| { |
| bInsert = true; |
| } |
| break; |
| case PDFWriter::RowSpan: |
| case PDFWriter::ColSpan: |
| // only for table cells |
| if( eType == PDFWriter::TableHeader || |
| eType == PDFWriter::TableData ) |
| { |
| bInsert = true; |
| } |
| break; |
| case PDFWriter::LinkAnnotation: |
| if( eType == PDFWriter::Link ) |
| bInsert = true; |
| break; |
| default: break; |
| } |
| } |
| |
| if( bInsert ) |
| m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue ); |
| #if OSL_DEBUG_LEVEL > 1 |
| else if( m_nCurrentStructElement > 0 && m_bEmitStructure ) |
| fprintf( stderr, "rejecting setStructureAttributeNumerical( %s, %d ) on %s (%s) element\n", |
| getAttributeTag( eAttr ), |
| (int)nValue, |
| getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ), |
| m_aStructure[ m_nCurrentStructElement ].m_aAlias.getStr() ); |
| #endif |
| |
| return bInsert; |
| } |
| |
| void PDFWriterImpl::setStructureBoundingBox( const Rectangle& rRect ) |
| { |
| sal_Int32 nPageNr = m_nCurrentPage; |
| if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() || !m_aContext.Tagged ) |
| return; |
| |
| |
| if( m_nCurrentStructElement > 0 && m_bEmitStructure ) |
| { |
| PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType; |
| if( eType == PDFWriter::Figure || |
| eType == PDFWriter::Formula || |
| eType == PDFWriter::Form || |
| eType == PDFWriter::Table ) |
| { |
| m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect; |
| // convert to default user space now, since the mapmode may change |
| m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox ); |
| } |
| } |
| } |
| |
| void PDFWriterImpl::setActualText( const String& rText ) |
| { |
| if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure ) |
| { |
| m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText; |
| } |
| } |
| |
| void PDFWriterImpl::setAlternateText( const String& rText ) |
| { |
| if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure ) |
| { |
| m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText; |
| } |
| } |
| |
| void PDFWriterImpl::setAutoAdvanceTime( sal_uInt32 nSeconds, sal_Int32 nPageNr ) |
| { |
| if( nPageNr < 0 ) |
| nPageNr = m_nCurrentPage; |
| |
| if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() ) |
| return; |
| |
| m_aPages[ nPageNr ].m_nDuration = nSeconds; |
| } |
| |
| void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr ) |
| { |
| if( nPageNr < 0 ) |
| nPageNr = m_nCurrentPage; |
| |
| if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() ) |
| return; |
| |
| m_aPages[ nPageNr ].m_eTransition = eType; |
| m_aPages[ nPageNr ].m_nTransTime = nMilliSec; |
| } |
| |
| void PDFWriterImpl::ensureUniqueRadioOnValues() |
| { |
| // loop over radio groups |
| for( std::map<sal_Int32,sal_Int32>::const_iterator group = m_aRadioGroupWidgets.begin(); |
| group != m_aRadioGroupWidgets.end(); ++group ) |
| { |
| PDFWidget& rGroupWidget = m_aWidgets[ group->second ]; |
| // check whether all kids have a unique OnValue |
| std::hash_map< OUString, sal_Int32, OUStringHash > aOnValues; |
| int nChildren = rGroupWidget.m_aKidsIndex.size(); |
| bool bIsUnique = true; |
| for( int nKid = 0; nKid < nChildren && bIsUnique; nKid++ ) |
| { |
| int nKidIndex = rGroupWidget.m_aKidsIndex[nKid]; |
| const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue; |
| #if OSL_DEBUG_LEVEL > 1 |
| fprintf( stderr, "OnValue: %s\n", OUStringToOString( rVal, RTL_TEXTENCODING_UTF8 ).getStr() ); |
| #endif |
| if( aOnValues.find( rVal ) == aOnValues.end() ) |
| { |
| aOnValues[ rVal ] = 1; |
| } |
| else |
| { |
| bIsUnique = false; |
| } |
| } |
| if( ! bIsUnique ) |
| { |
| #if OSL_DEBUG_LEVEL > 1 |
| fprintf( stderr, "enforcing unique OnValues\n" ); |
| #endif |
| // make unique by using ascending OnValues |
| for( int nKid = 0; nKid < nChildren; nKid++ ) |
| { |
| int nKidIndex = rGroupWidget.m_aKidsIndex[nKid]; |
| PDFWidget& rKid = m_aWidgets[nKidIndex]; |
| rKid.m_aOnValue = OUString::valueOf( sal_Int32(nKid+1) ); |
| if( ! rKid.m_aValue.equalsAscii( "Off" ) ) |
| rKid.m_aValue = rKid.m_aOnValue; |
| } |
| } |
| // finally move the "Yes" appearance to the OnValue appearance |
| for( int nKid = 0; nKid < nChildren; nKid++ ) |
| { |
| int nKidIndex = rGroupWidget.m_aKidsIndex[nKid]; |
| PDFWidget& rKid = m_aWidgets[nKidIndex]; |
| PDFAppearanceMap::iterator app_it = rKid.m_aAppearances.find( "N" ); |
| if( app_it != rKid.m_aAppearances.end() ) |
| { |
| PDFAppearanceStreams::iterator stream_it = app_it->second.find( "Yes" ); |
| if( stream_it != app_it->second.end() ) |
| { |
| SvMemoryStream* pStream = stream_it->second; |
| app_it->second.erase( stream_it ); |
| OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 ); |
| appendName( rKid.m_aOnValue, aBuf ); |
| (app_it->second)[ aBuf.makeStringAndClear() ] = pStream; |
| } |
| #if OSL_DEBUG_LEVEL > 1 |
| else |
| fprintf( stderr, "error: RadioButton without \"Yes\" stream\n" ); |
| #endif |
| } |
| // update selected radio button |
| if( ! rKid.m_aValue.equalsAscii( "Off" ) ) |
| { |
| rGroupWidget.m_aValue = rKid.m_aValue; |
| } |
| } |
| } |
| } |
| |
| sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn ) |
| { |
| sal_Int32 nRadioGroupWidget = -1; |
| |
| std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup ); |
| |
| if( it == m_aRadioGroupWidgets.end() ) |
| { |
| m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget = |
| sal_Int32(m_aWidgets.size()); |
| |
| // new group, insert the radiobutton |
| m_aWidgets.push_back( PDFWidget() ); |
| m_aWidgets.back().m_nObject = createObject(); |
| m_aWidgets.back().m_nPage = m_nCurrentPage; |
| m_aWidgets.back().m_eType = PDFWriter::RadioButton; |
| m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup; |
| m_aWidgets.back().m_nFlags |= 0x0000C000; // NoToggleToOff and Radio bits |
| |
| createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn ); |
| } |
| else |
| nRadioGroupWidget = it->second; |
| |
| return nRadioGroupWidget; |
| } |
| |
| sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr ) |
| { |
| if( nPageNr < 0 ) |
| nPageNr = m_nCurrentPage; |
| |
| if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() ) |
| return -1; |
| |
| sal_Int32 nNewWidget = m_aWidgets.size(); |
| m_aWidgets.push_back( PDFWidget() ); |
| |
| m_aWidgets.back().m_nObject = createObject(); |
| m_aWidgets.back().m_aRect = rControl.Location; |
| m_aWidgets.back().m_nPage = nPageNr; |
| m_aWidgets.back().m_eType = rControl.getType(); |
| |
| sal_Int32 nRadioGroupWidget = -1; |
| // for unknown reasons the radio buttons of a radio group must not have a |
| // field name, else the buttons are in fact check boxes - |
| // that is multiple buttons of the radio group can be selected |
| if( rControl.getType() == PDFWriter::RadioButton ) |
| nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) ); |
| else |
| { |
| createWidgetFieldName( nNewWidget, rControl ); |
| } |
| |
| // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid |
| PDFWidget& rNewWidget = m_aWidgets[nNewWidget]; |
| rNewWidget.m_aDescription = rControl.Description; |
| rNewWidget.m_aText = rControl.Text; |
| rNewWidget.m_nTextStyle = rControl.TextStyle & |
| ( TEXT_DRAW_LEFT | TEXT_DRAW_CENTER | TEXT_DRAW_RIGHT | TEXT_DRAW_TOP | |
| TEXT_DRAW_VCENTER | TEXT_DRAW_BOTTOM | |
| TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK ); |
| rNewWidget.m_nTabOrder = rControl.TabOrder; |
| |
| // various properties are set via the flags (/Ff) property of the field dict |
| if( rControl.ReadOnly ) |
| rNewWidget.m_nFlags |= 1; |
| if( rControl.getType() == PDFWriter::PushButton ) |
| { |
| const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl); |
| if( rNewWidget.m_nTextStyle == 0 ) |
| rNewWidget.m_nTextStyle = |
| TEXT_DRAW_CENTER | TEXT_DRAW_VCENTER | |
| TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK; |
| |
| rNewWidget.m_nFlags |= 0x00010000; |
| if( rBtn.URL.getLength() ) |
| rNewWidget.m_aListEntries.push_back( rBtn.URL ); |
| rNewWidget.m_bSubmit = rBtn.Submit; |
| rNewWidget.m_bSubmitGet = rBtn.SubmitGet; |
| rNewWidget.m_nDest = rBtn.Dest; |
| createDefaultPushButtonAppearance( rNewWidget, rBtn ); |
| } |
| else if( rControl.getType() == PDFWriter::RadioButton ) |
| { |
| const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl); |
| if( rNewWidget.m_nTextStyle == 0 ) |
| rNewWidget.m_nTextStyle = |
| TEXT_DRAW_VCENTER | TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK; |
| /* PDF sees a RadioButton group as one radio button with |
| * children which are in turn check boxes |
| * |
| * so we need to create a radio button on demand for a new group |
| * and insert a checkbox for each RadioButtonWidget as its child |
| */ |
| rNewWidget.m_eType = PDFWriter::CheckBox; |
| rNewWidget.m_nRadioGroup = rBtn.RadioGroup; |
| |
| DBG_ASSERT( nRadioGroupWidget >= 0 && nRadioGroupWidget < (sal_Int32)m_aWidgets.size(), "no radio group parent" ); |
| |
| PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget]; |
| rRadioButton.m_aKids.push_back( rNewWidget.m_nObject ); |
| rRadioButton.m_aKidsIndex.push_back( nNewWidget ); |
| rNewWidget.m_nParent = rRadioButton.m_nObject; |
| |
| rNewWidget.m_aValue = OUString( RTL_CONSTASCII_USTRINGPARAM( "Off" ) ); |
| rNewWidget.m_aOnValue = rBtn.OnValue; |
| if( ! rRadioButton.m_aValue.getLength() && rBtn.Selected ) |
| { |
| rNewWidget.m_aValue = rNewWidget.m_aOnValue; |
| rRadioButton.m_aValue = rNewWidget.m_aOnValue; |
| } |
| createDefaultRadioButtonAppearance( rNewWidget, rBtn ); |
| |
| // union rect of radio group |
| Rectangle aRect = rNewWidget.m_aRect; |
| m_aPages[ nPageNr ].convertRect( aRect ); |
| rRadioButton.m_aRect.Union( aRect ); |
| } |
| else if( rControl.getType() == PDFWriter::CheckBox ) |
| { |
| const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl); |
| if( rNewWidget.m_nTextStyle == 0 ) |
| rNewWidget.m_nTextStyle = |
| TEXT_DRAW_VCENTER | TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK; |
| |
| rNewWidget.m_aValue = OUString::createFromAscii( rBox.Checked ? "Yes" : "Off" ); |
| // create default appearance before m_aRect gets transformed |
| createDefaultCheckBoxAppearance( rNewWidget, rBox ); |
| } |
| else if( rControl.getType() == PDFWriter::ListBox ) |
| { |
| if( rNewWidget.m_nTextStyle == 0 ) |
| rNewWidget.m_nTextStyle = TEXT_DRAW_VCENTER; |
| |
| const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl); |
| rNewWidget.m_aListEntries = rLstBox.Entries; |
| rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries; |
| rNewWidget.m_aValue = rLstBox.Text; |
| if( rLstBox.DropDown ) |
| rNewWidget.m_nFlags |= 0x00020000; |
| if( rLstBox.Sort ) |
| rNewWidget.m_nFlags |= 0x00080000; |
| if( rLstBox.MultiSelect && !rLstBox.DropDown && (int)m_aContext.Version > (int)PDFWriter::PDF_1_3 ) |
| rNewWidget.m_nFlags |= 0x00200000; |
| |
| createDefaultListBoxAppearance( rNewWidget, rLstBox ); |
| } |
| else if( rControl.getType() == PDFWriter::ComboBox ) |
| { |
| if( rNewWidget.m_nTextStyle == 0 ) |
| rNewWidget.m_nTextStyle = TEXT_DRAW_VCENTER; |
| |
| const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl); |
| rNewWidget.m_aValue = rBox.Text; |
| rNewWidget.m_aListEntries = rBox.Entries; |
| rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag |
| if( rBox.Sort ) |
| rNewWidget.m_nFlags |= 0x00080000; |
| |
| PDFWriter::ListBoxWidget aLBox; |
| aLBox.Name = rBox.Name; |
| aLBox.Description = rBox.Description; |
| aLBox.Text = rBox.Text; |
| aLBox.TextStyle = rBox.TextStyle; |
| aLBox.ReadOnly = rBox.ReadOnly; |
| aLBox.Border = rBox.Border; |
| aLBox.BorderColor = rBox.BorderColor; |
| aLBox.Background = rBox.Background; |
| aLBox.BackgroundColor = rBox.BackgroundColor; |
| aLBox.TextFont = rBox.TextFont; |
| aLBox.TextColor = rBox.TextColor; |
| aLBox.DropDown = true; |
| aLBox.Sort = rBox.Sort; |
| aLBox.MultiSelect = false; |
| aLBox.Entries = rBox.Entries; |
| |
| createDefaultListBoxAppearance( rNewWidget, aLBox ); |
| } |
| else if( rControl.getType() == PDFWriter::Edit ) |
| { |
| if( rNewWidget.m_nTextStyle == 0 ) |
| rNewWidget.m_nTextStyle = TEXT_DRAW_LEFT | TEXT_DRAW_VCENTER; |
| |
| const PDFWriter::EditWidget& rEdit = static_cast<const PDFWriter::EditWidget&>(rControl); |
| if( rEdit.MultiLine ) |
| { |
| rNewWidget.m_nFlags |= 0x00001000; |
| rNewWidget.m_nTextStyle |= TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK; |
| } |
| if( rEdit.Password ) |
| rNewWidget.m_nFlags |= 0x00002000; |
| if( rEdit.FileSelect && m_aContext.Version > PDFWriter::PDF_1_3 ) |
| rNewWidget.m_nFlags |= 0x00100000; |
| rNewWidget.m_nMaxLen = rEdit.MaxLen; |
| rNewWidget.m_aValue = rEdit.Text; |
| |
| createDefaultEditAppearance( rNewWidget, rEdit ); |
| } |
| |
| // convert to default user space now, since the mapmode may change |
| // note: create default appearances before m_aRect gets transformed |
| m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect ); |
| |
| // insert widget to page's annotation list |
| m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject ); |
| |
| // mark page as having widgets |
| m_aPages[ nPageNr ].m_bHasWidgets = true; |
| |
| return nNewWidget; |
| } |
| |
| void PDFWriterImpl::beginControlAppearance( sal_Int32 nControl ) |
| { |
| if( nControl < 0 || nControl >= (sal_Int32)m_aWidgets.size() ) |
| return; |
| |
| PDFWidget& rWidget = m_aWidgets[ nControl ]; |
| m_nCurrentControl = nControl; |
| |
| SvMemoryStream* pControlStream = new SvMemoryStream( 1024, 1024 ); |
| // back conversion of control rect to current MapMode; necessary because |
| // MapMode between createControl and beginControlAppearance |
| // could have changed; therefore the widget rectangle is |
| // already converted |
| Rectangle aBack( Point( rWidget.m_aRect.Left(), pointToPixel(m_aPages[m_nCurrentPage].getHeight()) - rWidget.m_aRect.Top() - rWidget.m_aRect.GetHeight() ), |
| rWidget.m_aRect.GetSize() ); |
| aBack = lcl_convert( m_aMapMode, |
| m_aGraphicsStack.front().m_aMapMode, |
| getReferenceDevice(), |
| aBack ); |
| beginRedirect( pControlStream, aBack ); |
| writeBuffer( "/Tx BMC\n", 8 ); |
| } |
| |
| bool PDFWriterImpl::endControlAppearance( PDFWriter::WidgetState eState ) |
| { |
| bool bRet = false; |
| if( ! m_aOutputStreams.empty() ) |
| writeBuffer( "\nEMC\n", 5 ); |
| SvMemoryStream* pAppearance = static_cast<SvMemoryStream*>(endRedirect()); |
| if( pAppearance && m_nCurrentControl >= 0 && m_nCurrentControl < (sal_Int32)m_aWidgets.size() ) |
| { |
| PDFWidget& rWidget = m_aWidgets[ m_nCurrentControl ]; |
| OString aState, aStyle; |
| switch( rWidget.m_eType ) |
| { |
| case PDFWriter::PushButton: |
| if( eState == PDFWriter::Up || eState == PDFWriter::Down ) |
| { |
| aState = (eState == PDFWriter::Up) ? "N" : "D"; |
| aStyle = "Standard"; |
| } |
| break; |
| case PDFWriter::CheckBox: |
| if( eState == PDFWriter::Up || eState == PDFWriter::Down ) |
| { |
| aState = "N"; |
| aStyle = (eState == PDFWriter::Up) ? "Off" : "Yes"; |
| /* cf PDFReference 3rd ed. V1.4 p539: |
| recommended name for on state is "Yes", |
| recommended name for off state is "Off" |
| */ |
| } |
| break; |
| case PDFWriter::RadioButton: |
| if( eState == PDFWriter::Up || eState == PDFWriter::Down ) |
| { |
| aState = "N"; |
| if( eState == PDFWriter::Up ) |
| aStyle = "Off"; |
| else |
| { |
| OStringBuffer aBuf( rWidget.m_aOnValue.getLength()*2 ); |
| appendName( rWidget.m_aOnValue, aBuf ); |
| aStyle = aBuf.makeStringAndClear(); |
| } |
| } |
| break; |
| case PDFWriter::Edit: |
| aState = "N"; |
| aStyle = "Standard"; |
| break; |
| case PDFWriter::ListBox: |
| case PDFWriter::ComboBox: |
| case PDFWriter::Hierarchy: |
| break; |
| } |
| if( aState.getLength() && aStyle.getLength() ) |
| { |
| // delete eventual existing stream |
| PDFAppearanceStreams::iterator it = |
| rWidget.m_aAppearances[ aState ].find( aStyle ); |
| if( it != rWidget.m_aAppearances[ aState ].end() ) |
| delete it->second; |
| rWidget.m_aAppearances[ aState ][ aStyle ] = pAppearance; |
| bRet = true; |
| } |
| } |
| |
| if( ! bRet ) |
| delete pAppearance; |
| |
| m_nCurrentControl = -1; |
| |
| return bRet; |
| } |
| |
| void PDFWriterImpl::addStream( const String& rMimeType, PDFOutputStream* pStream, bool bCompress ) |
| { |
| if( pStream ) |
| { |
| m_aAdditionalStreams.push_back( PDFAddStream() ); |
| PDFAddStream& rStream = m_aAdditionalStreams.back(); |
| rStream.m_aMimeType = rMimeType.Len() |
| ? OUString( rMimeType ) |
| : OUString( RTL_CONSTASCII_USTRINGPARAM( "application/octet-stream" ) ); |
| rStream.m_pStream = pStream; |
| rStream.m_bCompress = bCompress; |
| } |
| } |
| |
| |
| |