diff --git a/PDFWriter/AbstractWrittenFont.cpp b/PDFWriter/AbstractWrittenFont.cpp index 53254800..94f954ac 100644 --- a/PDFWriter/AbstractWrittenFont.cpp +++ b/PDFWriter/AbstractWrittenFont.cpp @@ -36,9 +36,10 @@ using namespace PDFHummus; -AbstractWrittenFont::AbstractWrittenFont(ObjectsContext* inObjectsContext) +AbstractWrittenFont::AbstractWrittenFont(ObjectsContext* inObjectsContext, FreeTypeFaceWrapper* inFontInfo) { mObjectsContext = inObjectsContext; + mFontInfo = inFontInfo; mCIDRepresentation = NULL; mANSIRepresentation = NULL; } diff --git a/PDFWriter/AbstractWrittenFont.h b/PDFWriter/AbstractWrittenFont.h index aaa18d19..4bb54e4d 100644 --- a/PDFWriter/AbstractWrittenFont.h +++ b/PDFWriter/AbstractWrittenFont.h @@ -32,7 +32,7 @@ class PDFParser; class AbstractWrittenFont : public IWrittenFont { public: - AbstractWrittenFont(ObjectsContext* inObjectsContext); + AbstractWrittenFont(ObjectsContext* inObjectsContext, FreeTypeFaceWrapper* inFontInfo); virtual ~AbstractWrittenFont(void); virtual void AppendGlyphs(const GlyphUnicodeMappingList& inGlyphsList, @@ -47,6 +47,7 @@ class AbstractWrittenFont : public IWrittenFont WrittenFontRepresentation* mCIDRepresentation; WrittenFontRepresentation* mANSIRepresentation; ObjectsContext* mObjectsContext; + FreeTypeFaceWrapper* mFontInfo; PDFHummus::EStatusCode WriteStateInDictionary(ObjectsContext* inStateWriter,DictionaryContext* inDerivedObjectDictionary); PDFHummus::EStatusCode WriteStateAfterDictionary(ObjectsContext* inStateWriter); diff --git a/PDFWriter/FreeTypeFaceWrapper.cpp b/PDFWriter/FreeTypeFaceWrapper.cpp index d6ce0f98..75431bb5 100644 --- a/PDFWriter/FreeTypeFaceWrapper.cpp +++ b/PDFWriter/FreeTypeFaceWrapper.cpp @@ -655,11 +655,11 @@ IWrittenFont* FreeTypeFaceWrapper::CreateWrittenFontObject(ObjectsContext* inObj if(FT_Get_CID_Is_Internally_CID_Keyed(mFace,&isCID) != 0) isCID = false; - result = new WrittenFontCFF(inObjectsContext,isCID != 0, inFontIsToBeEmbedded); // CFF fonts should know if font is to be embedded, as the embedding code involves re-encoding of glyphs + result = new WrittenFontCFF(inObjectsContext, this,isCID != 0, inFontIsToBeEmbedded); // CFF fonts should know if font is to be embedded, as the embedding code involves re-encoding of glyphs } else if(strcmp(fontFormat,scTrueType) == 0) { - result = new WrittenFontTrueType(inObjectsContext); + result = new WrittenFontTrueType(inObjectsContext, this); } else { diff --git a/PDFWriter/IWrittenFont.h b/PDFWriter/IWrittenFont.h index d9f0f57f..3c1d471b 100644 --- a/PDFWriter/IWrittenFont.h +++ b/PDFWriter/IWrittenFont.h @@ -61,7 +61,7 @@ class IWrittenFont /* Write a font definition using the glyphs appended. */ - virtual PDFHummus::EStatusCode WriteFontDefinition(FreeTypeFaceWrapper& inFontInfo,bool inEmbedFont) = 0; + virtual PDFHummus::EStatusCode WriteFontDefinition(bool inEmbedFont) = 0; // state read and write virtual PDFHummus::EStatusCode WriteState(ObjectsContext* inStateWriter,ObjectIDType inObjectID) = 0; diff --git a/PDFWriter/PDFUsedFont.cpp b/PDFWriter/PDFUsedFont.cpp index a6fa463f..ec3fcd34 100644 --- a/PDFWriter/PDFUsedFont.cpp +++ b/PDFWriter/PDFUsedFont.cpp @@ -122,7 +122,7 @@ EStatusCode PDFUsedFont::WriteFontDefinition() if(!mWrittenFont) return eSuccess; else - return mWrittenFont->WriteFontDefinition(mFaceWrapper, mEmbedFont); + return mWrittenFont->WriteFontDefinition(mEmbedFont); } EStatusCode PDFUsedFont::WriteState(ObjectsContext* inStateWriter,ObjectIDType inObjectID) diff --git a/PDFWriter/WrittenFontCFF.cpp b/PDFWriter/WrittenFontCFF.cpp index 2795f289..0d495bd0 100644 --- a/PDFWriter/WrittenFontCFF.cpp +++ b/PDFWriter/WrittenFontCFF.cpp @@ -34,7 +34,7 @@ using namespace PDFHummus; -WrittenFontCFF::WrittenFontCFF(ObjectsContext* inObjectsContext,bool inIsCID, bool inFontWillBeEmbedded):AbstractWrittenFont(inObjectsContext) +WrittenFontCFF::WrittenFontCFF(ObjectsContext* inObjectsContext, FreeTypeFaceWrapper* inFontInfo, bool inIsCID, bool inFontWillBeEmbedded):AbstractWrittenFont(inObjectsContext, inFontInfo) { mAvailablePositionsCount = 255; mFreeList.push_back(UCharAndUChar(1,255)); @@ -161,7 +161,7 @@ unsigned char WrittenFontCFF::AllocateFromFreeList(unsigned int inGlyph) return result; } -EStatusCode WrittenFontCFF::WriteFontDefinition(FreeTypeFaceWrapper& inFontInfo,bool inEmbedFont) +EStatusCode WrittenFontCFF::WriteFontDefinition(bool inEmbedFont) { EStatusCode status = PDFHummus::eSuccess; do @@ -170,7 +170,7 @@ EStatusCode WrittenFontCFF::WriteFontDefinition(FreeTypeFaceWrapper& inFontInfo, { CFFANSIFontWriter fontWriter; - status = fontWriter.WriteFont(inFontInfo, mANSIRepresentation, mObjectsContext, inEmbedFont); + status = fontWriter.WriteFont(*mFontInfo, mANSIRepresentation, mObjectsContext, inEmbedFont); if(status != PDFHummus::eSuccess) { TRACE_LOG("WrittenFontCFF::WriteFontDefinition, Failed to write Ansi font definition"); @@ -184,7 +184,7 @@ EStatusCode WrittenFontCFF::WriteFontDefinition(FreeTypeFaceWrapper& inFontInfo, CIDFontWriter fontWriter; CFFDescendentFontWriter descendentFontWriter; - status = fontWriter.WriteFont(inFontInfo, mCIDRepresentation, mObjectsContext, &descendentFontWriter, inEmbedFont); + status = fontWriter.WriteFont(*mFontInfo, mCIDRepresentation, mObjectsContext, &descendentFontWriter, inEmbedFont); if(status != PDFHummus::eSuccess) { TRACE_LOG("WrittenFontCFF::WriteFontDefinition, Failed to write CID font definition"); diff --git a/PDFWriter/WrittenFontCFF.h b/PDFWriter/WrittenFontCFF.h index 3996dc9b..f7157728 100644 --- a/PDFWriter/WrittenFontCFF.h +++ b/PDFWriter/WrittenFontCFF.h @@ -33,11 +33,11 @@ typedef std::list UCharAndUCharList; class WrittenFontCFF : public AbstractWrittenFont { public: - WrittenFontCFF(ObjectsContext* inObjectsContext,bool inIsCID, bool inFontWillBeEmbedded); + WrittenFontCFF(ObjectsContext* inObjectsContext, FreeTypeFaceWrapper* inFontInfo, bool inIsCID, bool inFontWillBeEmbedded); virtual ~WrittenFontCFF(void); - virtual PDFHummus::EStatusCode WriteFontDefinition(FreeTypeFaceWrapper& inFontInfo, bool inEmbedFont); + virtual PDFHummus::EStatusCode WriteFontDefinition(bool inEmbedFont); virtual PDFHummus::EStatusCode WriteState(ObjectsContext* inStateWriter,ObjectIDType inObjectId); virtual PDFHummus::EStatusCode ReadState(PDFParser* inStateReader,ObjectIDType inObjectID); diff --git a/PDFWriter/WrittenFontTrueType.cpp b/PDFWriter/WrittenFontTrueType.cpp index 079815d6..a1db5636 100644 --- a/PDFWriter/WrittenFontTrueType.cpp +++ b/PDFWriter/WrittenFontTrueType.cpp @@ -29,11 +29,33 @@ #include "PDFObjectCast.h" #include "PDFParser.h" #include "PDFDictionary.h" +#include "FreeTypeFaceWrapper.h" + +#include +#include FT_FREETYPE_H using namespace PDFHummus; -WrittenFontTrueType::WrittenFontTrueType(ObjectsContext* inObjectsContext):AbstractWrittenFont(inObjectsContext) +bool FontHasCmapsForWinAnsiEncoding(FT_Face font) { + // See PDF Reference 5.5.5 Character Encoding, Encodings for TrueType Fonts. When is mapping from glyph name is possible, and so non CID encoding can be built - only if font contains either of + // two possible cmaps: win unicode bmp or macintosh roman. so check if got either of those. + for(FT_Int i = 0; i < font->num_charmaps; ++i) { + FT_CharMap charmap = font->charmaps[i]; + + if (charmap->platform_id == 3 && charmap->encoding_id == 1) + return true; // Windows Unicode BMP + + if (charmap->platform_id == 1 && charmap->encoding_id == 0) + return true; // Macintosh Roman + + } + + return false; +} + +WrittenFontTrueType::WrittenFontTrueType(ObjectsContext* inObjectsContext, FreeTypeFaceWrapper* inFontInfo):AbstractWrittenFont(inObjectsContext, inFontInfo) { + fontSupportsWinAnsiEncoding = FontHasCmapsForWinAnsiEncoding(*inFontInfo); } WrittenFontTrueType::~WrittenFontTrueType(void) @@ -43,18 +65,19 @@ WrittenFontTrueType::~WrittenFontTrueType(void) /* here's what i'm deciding on: 1. Can encoding if/f all text codes are available through WinAnsiEncoding. -[maybe should also make sure that the font has the relevant cmaps?! Or maybe I'm just assuming that...] +[maybe should also make sure that the font has the relevant cmaps?! Or maybe I'm just assuming that...] [That's what FontHasCmapsForWinAnsiEncoding and fontSupportsWinAnsiEncoding are for] 2. While encoding use WinAnsiEncoding values, of course. This will necasserily work 3. While writing the font description simply write the WinAnsiEncoding glyph name, and pray.*/ -bool WrittenFontTrueType::AddToANSIRepresentation( const GlyphUnicodeMappingList& inGlyphsList, - UShortList& outEncodedCharacters) -{ +bool WrittenFontTrueType::AddANSICandidates(const GlyphUnicodeMappingList& inGlyphsList, UShortList& ioCandidates) { + if(!fontSupportsWinAnsiEncoding) { + return false; + } + // i'm totally relying on the text here, which is fine till i'll do ligatures, in which case // i'll need to make something different out of the text. // as you can see this has little to do with glyphs (mainly cause i can't use FreeType to map the glyphs // back to the rleevant unicode values...but no need anyways...that's why i carry the text). - UShortList candidates; BoolAndByte encodingResult(true,0); WinAnsiEncoding winAnsiEncoding; GlyphUnicodeMappingList::const_iterator it = inGlyphsList.begin(); @@ -81,11 +104,20 @@ bool WrittenFontTrueType::AddToANSIRepresentation( const GlyphUnicodeMappingList { encodingResult = winAnsiEncoding.Encode(it->mUnicodeValues.front()); if(encodingResult.first) - candidates.push_back(encodingResult.second); + ioCandidates.push_back(encodingResult.second); } } - if(encodingResult.first) + return encodingResult.first; +} + +bool WrittenFontTrueType::AddToANSIRepresentation(const GlyphUnicodeMappingList& inGlyphsList, UShortList& outEncodedCharacters) +{ + UShortList candidates; + + bool result = AddANSICandidates(inGlyphsList, candidates); + + if(result) { // for the first time, add also 0,0 mapping if(mANSIRepresentation->mGlyphIDToEncodedChar.size() == 0) @@ -104,11 +136,11 @@ bool WrittenFontTrueType::AddToANSIRepresentation( const GlyphUnicodeMappingList outEncodedCharacters = candidates; } - return encodingResult.first; + return result; } -EStatusCode WrittenFontTrueType::WriteFontDefinition(FreeTypeFaceWrapper& inFontInfo,bool inEmbedFont) +EStatusCode WrittenFontTrueType::WriteFontDefinition(bool inEmbedFont) { EStatusCode status = PDFHummus::eSuccess; do @@ -117,7 +149,7 @@ EStatusCode WrittenFontTrueType::WriteFontDefinition(FreeTypeFaceWrapper& inFont { TrueTypeANSIFontWriter fontWriter; - status = fontWriter.WriteFont(inFontInfo, mANSIRepresentation, mObjectsContext, inEmbedFont); + status = fontWriter.WriteFont(*mFontInfo, mANSIRepresentation, mObjectsContext, inEmbedFont); if(status != PDFHummus::eSuccess) { TRACE_LOG("WrittenFontTrueType::WriteFontDefinition, Failed to write Ansi font definition"); @@ -131,7 +163,7 @@ EStatusCode WrittenFontTrueType::WriteFontDefinition(FreeTypeFaceWrapper& inFont CIDFontWriter fontWriter; TrueTypeDescendentFontWriter descendentFontWriter; - status = fontWriter.WriteFont(inFontInfo, mCIDRepresentation, mObjectsContext, &descendentFontWriter, inEmbedFont); + status = fontWriter.WriteFont(*mFontInfo, mCIDRepresentation, mObjectsContext, &descendentFontWriter, inEmbedFont); if(status != PDFHummus::eSuccess) { TRACE_LOG("WrittenFontTrueType::WriteFontDefinition, Failed to write CID font definition"); @@ -150,46 +182,20 @@ bool WrittenFontTrueType::AddToANSIRepresentation( const GlyphUnicodeMappingList UShortListList candidatesList; UShortList candidates; BoolAndByte encodingResult(true,0); - WinAnsiEncoding winAnsiEncoding; GlyphUnicodeMappingListList::const_iterator itList = inGlyphsList.begin(); - GlyphUnicodeMappingList::const_iterator it; + bool result = true; - for(; itList != inGlyphsList.end() && encodingResult.first; ++itList) + for(; itList != inGlyphsList.end() && result; ++itList) { - it = itList->begin(); - for(; it != itList->end() && encodingResult.first; ++it) - { - // don't bother with characters of more or less than one unicode - if(it->mUnicodeValues.size() != 1) - { - encodingResult.first = false; - } - else if(0x2022 == it->mUnicodeValues.front()) - { - // From the reference: - // In WinAnsiEncoding, all unused codes greater than 40 map to the bullet character. - // However, only code 225 is specifically assigned to the bullet character; other codes are subject to future reassignment. - - // now i don't know if it's related or not...but acrobat isn't happy when i'm using winansi with bullet. and text coming after that bullet may be - // corrupted. - // so i'm forcing CID if i hit bullet till i know better. - encodingResult.first = false; - } - else - { - encodingResult = winAnsiEncoding.Encode(it->mUnicodeValues.front()); - if(encodingResult.first) - candidates.push_back(encodingResult.second); - } - } - if(encodingResult.first) + result = AddANSICandidates(*itList, candidates); + if(result) { candidatesList.push_back(candidates); candidates.clear(); } } - if(encodingResult.first) + if(result) { // for the first time, add also 0,0 mapping if(mANSIRepresentation->mGlyphIDToEncodedChar.size() == 0) @@ -216,7 +222,7 @@ bool WrittenFontTrueType::AddToANSIRepresentation( const GlyphUnicodeMappingList outEncodedCharacters = candidatesList; } - return encodingResult.first; + return result; } EStatusCode WrittenFontTrueType::WriteState(ObjectsContext* inStateWriter,ObjectIDType inObjectID) @@ -246,9 +252,9 @@ EStatusCode WrittenFontTrueType::ReadState(PDFParser* inStateReader,ObjectIDType return AbstractWrittenFont::ReadStateFromObject(inStateReader,writtenFontState.GetPtr()); } -unsigned short WrittenFontTrueType::EncodeCIDGlyph(unsigned int inGlyphId) { - // Gal 26/8/2017: Most of the times, the glyph IDs are CIDs. this is to retain a few requirements of True type fonts, and the case of fonts when they are not embedded. - // However, when CFF fonts are embedded, the matching code actually recreates a font from just the subset, and renumbers them based on the order - // of them joining the font. Hence, we need a slight difference for this case, and an override is provided - return (unsigned short)inGlyphId; +unsigned short WrittenFontTrueType::EncodeCIDGlyph(unsigned int inGlyphId) { + // Gal 26/8/2017: Most of the times, the glyph IDs are CIDs. this is to retain a few requirements of True type fonts, and the case of fonts when they are not embedded. + // However, when CFF fonts are embedded, the matching code actually recreates a font from just the subset, and renumbers them based on the order + // of them joining the font. Hence, we need a slight difference for this case, and an override is provided + return (unsigned short)inGlyphId; } \ No newline at end of file diff --git a/PDFWriter/WrittenFontTrueType.h b/PDFWriter/WrittenFontTrueType.h index 33a235af..e6cb5173 100644 --- a/PDFWriter/WrittenFontTrueType.h +++ b/PDFWriter/WrittenFontTrueType.h @@ -24,16 +24,18 @@ class WrittenFontTrueType : public AbstractWrittenFont { public: - WrittenFontTrueType(ObjectsContext* inObjectsContext); + WrittenFontTrueType(ObjectsContext* inObjectsContext, FreeTypeFaceWrapper* inFontInfo); ~WrittenFontTrueType(void); - virtual PDFHummus::EStatusCode WriteFontDefinition(FreeTypeFaceWrapper& inFontInfo,bool inEmbedFont); + virtual PDFHummus::EStatusCode WriteFontDefinition(bool inEmbedFont); virtual PDFHummus::EStatusCode WriteState(ObjectsContext* inStateWriter,ObjectIDType inObjectId); virtual PDFHummus::EStatusCode ReadState(PDFParser* inStateReader,ObjectIDType inObjectID); private: + bool fontSupportsWinAnsiEncoding; + virtual bool AddToANSIRepresentation( const GlyphUnicodeMappingList& inGlyphsList, UShortList& outEncodedCharacters); @@ -44,4 +46,8 @@ class WrittenFontTrueType : public AbstractWrittenFont virtual unsigned short EncodeCIDGlyph(unsigned int inGlyphId); + bool AddANSICandidates(const GlyphUnicodeMappingList& inGlyphsList, UShortList& ioCandidates); + + + }; diff --git a/PDFWriterTesting/CMakeLists.txt b/PDFWriterTesting/CMakeLists.txt index f082ad69..5a233c32 100644 --- a/PDFWriterTesting/CMakeLists.txt +++ b/PDFWriterTesting/CMakeLists.txt @@ -67,6 +67,7 @@ create_test_sourcelist (Tests TIFFImageTest.cpp TiffSpecialsTest.cpp TimerTest.cpp + TrueTypeAnsiWriteBug.cpp TrueTypeTest.cpp TTCTest.cpp Type1Test.cpp diff --git a/PDFWriterTesting/Materials/fonts/HelveticaNeue.ttc b/PDFWriterTesting/Materials/fonts/HelveticaNeue.ttc new file mode 100644 index 00000000..8be1d232 Binary files /dev/null and b/PDFWriterTesting/Materials/fonts/HelveticaNeue.ttc differ diff --git a/PDFWriterTesting/TrueTypeAnsiWriteBug.cpp b/PDFWriterTesting/TrueTypeAnsiWriteBug.cpp new file mode 100644 index 00000000..cc7cab32 --- /dev/null +++ b/PDFWriterTesting/TrueTypeAnsiWriteBug.cpp @@ -0,0 +1,105 @@ +/* + Source File : CIDSetWritingCFF.cpp + + + Copyright 2011 Gal Kahana PDFWriter + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +*/ +#include "PDFWriter.h" +#include "PDFPage.h" +#include "PageContentContext.h" +#include "PDFUsedFont.h" + +#include + +#include "testing/TestIO.h" + +using namespace PDFHummus; + + +int TrueTypeAnsiWriteBug(int argc, char* argv[]) +{ + EStatusCode status = eSuccess; + PDFWriter pdfWriter; + + do + { + status = pdfWriter.StartPDF(BuildRelativeOutputPath(argv,"TrueTypeAnsiWriteBug.pdf"),ePDFVersion14); + if(status != eSuccess) + { + cout<<"Failed to start file\n"; + break; + } + + PDFPage* page = new PDFPage(); + page->SetMediaBox(PDFRectangle(0,0,595,842)); + + PageContentContext* cxt = pdfWriter.StartPageContentContext(page); + + AbstractContentContext::TextOptions textOptions(pdfWriter.GetFontForFile( + BuildRelativeInputPath( + argv, + "fonts/HelveticaNeue.ttc")), + 14, + AbstractContentContext::eGray, + 0); + + cxt->WriteText(75,600,"Helvetica Neue",textOptions); + + GlyphUnicodeMappingList guml; + // Helve, but with direct glyphs writing + guml.push_back(GlyphUnicodeMapping(47, 72)); + guml.push_back(GlyphUnicodeMapping(76, 101)); + guml.push_back(GlyphUnicodeMapping(83, 108)); + guml.push_back(GlyphUnicodeMapping(93, 118)); + guml.push_back(GlyphUnicodeMapping(76, 101)); + cxt->BT(); + cxt->k(0,0,0,1); + cxt->Tm(1,0,0,1,10,600); + cxt->Tf(textOptions.font, 14); + cxt->Tj(guml); + cxt->ET(); + + status = pdfWriter.EndPageContentContext(cxt); + if(status != eSuccess) + { + status = PDFHummus::eFailure; + cout<<"Failed to end content context\n"; + break; + } + + status = pdfWriter.WritePageAndRelease(page); + if(status != eSuccess) + { + status = PDFHummus::eFailure; + cout<<"Failed to write page\n"; + break; + } + + + status = pdfWriter.EndPDF(); + if(status != eSuccess) + { + status = PDFHummus::eFailure; + cout<<"Failed to end pdf\n"; + break; + } + + }while(false); + + + return status == eSuccess ? 0:1; +}