// -------------------------------------------------------------------- // Interface with Pdflatex // -------------------------------------------------------------------- /* This file is part of the extensible drawing editor Ipe. Copyright (C) 1993-2004 Otfried Cheong Ipe is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. As a special exception, you have permission to link Ipe with the CGAL library and distribute executables, as long as you follow the requirements of the Gnu General Public License in regard to all of the software in the executable aside from CGAL. Ipe is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Ipe; if not, you can find it at "http://www.gnu.org/copyleft/gpl.html", or write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "ipevisitor.h" #include "ipestyle.h" #include "ipegroup.h" #include "iperef.h" #include "ipefontpool.h" #include "ipelatex.h" /*! \class IpeLatex \brief Object that converts latex source to PDF format. This object is responsible for creating the PDF representation of text objects. */ //! Create a converter object. IpeLatex::IpeLatex() { iFontPool = 0; } //! Destructor. IpeLatex::~IpeLatex() { for (XFormList::iterator it = iXForms.begin(); it != iXForms.end(); ++it) delete *it; delete iFontPool; } // -------------------------------------------------------------------- //! Return the newly created font pool and pass ownership of pool to caller. IpeFontPool *IpeLatex::TakeFontPool() { IpeFontPool *pool = iFontPool; iFontPool = 0; return pool; } // -------------------------------------------------------------------- class TextCollectingVisitor : public IpeVisitor { public: TextCollectingVisitor(IpeLatex::TextList *list); virtual void VisitText(const IpeText *obj); virtual void VisitGroup(const IpeGroup *obj); virtual void VisitReference(const IpeReference *obj); public: bool iTextFound; private: IpeLatex::TextList *iList; IpeAttributeSeq iSize; }; TextCollectingVisitor::TextCollectingVisitor(IpeLatex::TextList *list) : iList(list) { iSize.push_back(IpeAttribute()); // null text size } void TextCollectingVisitor::VisitText(const IpeText *obj) { IpeLatex::SText s; s.iText = obj; if (iSize.back()) s.iSize = iSize.back(); else s.iSize = obj->Size(); iList->push_back(s); iTextFound = true; } void TextCollectingVisitor::VisitGroup(const IpeGroup *obj) { if (iSize.back()) iSize.push_back(iSize.back()); else iSize.push_back(obj->TextSize()); for (IpeGroup::const_iterator it = obj->begin(); it != obj->end(); ++it) (*it)->Accept(*this); iSize.pop_back(); } void TextCollectingVisitor::VisitReference(const IpeReference *obj) { const IpeObject *ref = obj->Object(); if (ref) ref->Accept(*this); } // -------------------------------------------------------------------- /*! Scan a page and insert all text objects into IpeLatex's list. Returns total number of text objects found so far. */ int IpeLatex::ScanPage(IpePage *page) { TextCollectingVisitor visitor(&iTextObjects); for (IpePage::const_iterator it = page->begin(); it != page->end(); ++it) { visitor.iTextFound = false; visitor(it->Object()); if (visitor.iTextFound) it->InvalidateBBox(); } return iTextObjects.size(); } /*! Create a Latex source file with all the text objects collected before. The client should have prepared a directory for the Pdflatex run, and pass the name of the Latex source file to be written by IpeLatex. Returns the number of text objects that did not yet have an XForm, or a negative error code. */ int IpeLatex::CreateLatexSource(IpeStream &stream, IpeString preamble, const IpeStyleSheet *sheet) { int count = 0; stream << "\\pdfcompresslevel0\n" << "\\nonstopmode\n" << "\\documentclass{article}\n" << "\\newcommand{\\Ipechar}[1]{\\unichar{#1}}\n" << "\\usepackage{color}\n"; IpeAttributeSeq colors; sheet->AllNames(IpeAttribute::EColor, colors); for (IpeAttributeSeq::const_iterator it = colors.begin(); it != colors.end(); ++it) { // only symbolic names (not black, white, void) IpeString name = sheet->Repository()->String(*it); IpeColor value = sheet->Repository()->ToColor(sheet->Find(*it)); if (value.IsGray()) stream << "\\definecolor{" << name << "}{gray}{" << value.iRed << "}\n"; else stream << "\\definecolor{" << name << "}{rgb}{" << value.iRed << "," << value.iGreen << "," << value.iBlue << "}\n"; } stream << sheet->TotalPreamble() << "\n" << preamble << "\n" << "\\pagestyle{empty}\n" << "\\newcount\\bigpoint\\dimen0=0.01bp\\bigpoint=\\dimen0\n" << "\\begin{document}\n" << "\\begin{picture}(500,500)\n"; for (TextList::iterator it = iTextObjects.begin(); it != iTextObjects.end(); ++it) { const IpeText *text = it->iText; if (!text->GetXForm()) count++; // skip objects with null or unknown symbolic size IpeAttribute fsAttr = sheet->Find(it->iSize); if (!fsAttr) continue; // compute x-stretch factor from textstretch IpeVector stretch(1.0, 1.0); if (it->iSize.IsSymbolic()) { IpeAttribute ts(IpeAttribute::ETextStretch, true, it->iSize.Index()); IpeAttribute tsAttr = sheet->Find(ts); if (tsAttr) stretch = sheet->Repository()->ToVector(tsAttr); } IpeAttribute color = text->Stroke(); IpeAttribute abs = sheet->Find(color); // invisible text objects: run with black color if (color.IsNullOrVoid() || abs.IsNull()) abs = IpeAttribute::Black(); IpeColor col = sheet->Repository()->ToColor(abs); stream << "\\setbox0=\\hbox{"; char ipeid[20]; // on 64-bit systems, pointers are 64 bit std::sprintf(ipeid, "/%08lx", (unsigned long int)(text)); if (text->IsMiniPage()) { stream << "\\begin{minipage}{" << text->Width()/stretch.iX << "bp}"; } if (fsAttr.IsNumeric()) { IpeFixed fs = fsAttr.Number(); stream << "\\fontsize{" << fs << "}" << "{" << fs.Mult(6, 5) << "bp}\\selectfont\n"; } else stream << sheet->Repository()->String(fsAttr) << "\n"; stream << "\\color[rgb]{" << col.iRed << "," << col.iGreen << "," << col.iBlue << "}%\n"; IpeString txt = text->Text(); for (int i = 0; i < txt.size(); ) { int uc = txt.unicode(i); // advances i if (uc < 0x80) stream << char(uc); else stream << "\\Ipechar{" << uc << "}"; } if (text->IsMiniPage()) stream << "\\end{minipage}"; stream << "}\n" << "\\count0=\\dp0\\divide\\count0 by \\bigpoint\n" << "\\pdfxform attr{/IpeId " << ipeid << " /IpeStretch [ " << stretch.iX << " " << stretch.iY << " ] " << " /IpeDepth \\the\\count0}" << "0\\put(0,0){\\pdfrefxform\\pdflastxform}\n"; } stream << "\\end{picture}\n\\end{document}\n"; return count; } bool IpeLatex::GetXForm(const IpePdfObj *xform) { const IpePdfDict *dict = xform->Dict(); if (!dict || dict->Stream().size() == 0) return false; IpeText::XForm *xf = new IpeText::XForm; iXForms.push_back(xf); assert(!dict->Deflated()); xf->iStream = dict->Stream(); /* Should we check /Matrix and /FormType? /Type /XObject /Subtype /Form /IpeId /abcd1234 /IpeDepth 246 /IpeStretch [ 3 3 ] /BBox [0 0 4.639 4.289] /FormType 1 /Matrix [1 0 0 1 0 0] /Resources 11 0 R */ // Get Ipe id const IpePdfObj *id = dict->Get("IpeId", &iPdf); if (!id || !id->Name()) return false; IpeLex lex(id->Name()->Value()); xf->iRefCount = lex.GetHexNumber(); // abusing refcount field const IpePdfObj *depth = dict->Get("IpeDepth", &iPdf); if (!depth || !depth->Number()) return false; xf->iDepth = int(depth->Number()->Value()); const IpePdfObj *stretch = dict->Get("IpeStretch", &iPdf); if (!stretch || !stretch->Array()) return false; const IpePdfObj *xstretch = stretch->Array()->Obj(0, &iPdf); const IpePdfObj *ystretch = stretch->Array()->Obj(1, &iPdf); if (!xstretch || !xstretch->Number() || !ystretch || !ystretch->Number()) return false; xf->iStretch = IpeVector(xstretch->Number()->Value(), ystretch->Number()->Value()); // Get BBox const IpePdfObj *bbox = dict->Get("BBox", &iPdf); if (!bbox || !bbox->Array()) return false; const IpePdfObj *a[4]; for (int i = 0; i < 4; i++) { a[i] = bbox->Array()->Obj(i, &iPdf); if (!a[i] || !a[i]->Number()) return false; } IpeVector bl(a[0]->Number()->Value(), a[1]->Number()->Value()); IpeVector sz(a[2]->Number()->Value(), a[3]->Number()->Value()); xf->iBBox.AddPoint(bl); xf->iBBox.AddPoint(bl + sz); if (xf->iBBox.Min() != IpeVector::Zero) { ipeDebug("PDF bounding box is not zero-aligned: (%g, %g)", xf->iBBox.Min().iX, xf->iBBox.Min().iY); return false; } const IpePdfObj *res = dict->Get("Resources", &iPdf); if (!res || !res->Dict()) { Warn("No /Resources in XForm."); return false; } /* /Font << /F8 9 0 R /F10 18 0 R >> /ProcSet [ /PDF /Text ] */ const IpePdfObj *fontDict = res->Dict()->Get("Font", &iPdf); if (fontDict && fontDict->Dict()) { int numFonts = fontDict->Dict()->Count(); xf->iFonts.resize(numFonts); for (int i = 0; i < numFonts; i++) { IpeString fontName(fontDict->Dict()->Key(i)); if (fontName[0] != 'F') { Warn(IpeString("Weird font name: ") + fontName); return false; } int fontNumber = IpeLex(fontName.substr(1)).GetInt(); xf->iFonts[i] = fontNumber; const IpePdfObj *fontRef = fontDict->Dict()->Get(fontName, 0); if (!fontRef || !fontRef->Ref()) { Warn("Font is not indirect"); return false; } iFontObjects[fontNumber] = fontRef->Ref()->Value(); } } return true; } bool IpeLatex::GetEmbeddedFont(int fno, int objno) { const IpePdfObj *fontObj = iPdf.Object(objno); /* /Type /Font /Subtype /Type1 /Encoding 24 0 R /FirstChar 6 /LastChar 49 /Widths 25 0 R /BaseFont /YEHLEP+CMR10 /FontDescriptor 7 0 R */ if (!fontObj || !fontObj->Dict()) return false; const IpePdfObj *type = fontObj->Dict()->Get("Type", 0); if (!type || !type->Name() || type->Name()->Value() != "Font") return false; // Check whether the stream data is already there. IpeFontPool::iterator it = iFontPool->begin(); while (it != iFontPool->end() && it->iLatexNumber != fno) ++it; if (it == iFontPool->end()) { iFontPool->push_back(IpeFont()); it = iFontPool->end() - 1; } IpeFont &font = *it; font.iLatexNumber = fno; // get font dictionary for (int i = 0; i < fontObj->Dict()->Count(); i++) { IpeString key(fontObj->Dict()->Key(i)); if (key != "FontDescriptor") { const IpePdfObj *data = fontObj->Dict()->Get(key, &iPdf); font.iFontDict += IpeString("/") + key + " " + data->Repr() + "\n"; } } // Get type const IpePdfObj *subtype = fontObj->Dict()->Get("Subtype", &iPdf); if (!subtype || !subtype->Name()) return false; if (subtype->Name()->Value() == "Type1") font.iType = IpeFont::EType1; else if (subtype->Name()->Value() == "TrueType") font.iType = IpeFont::ETrueType; else { Warn("Pdflatex has embedded a font of a type not supported by Ipe."); return false; } // Get encoding vector if (font.iType == IpeFont::EType1) { const IpePdfObj *enc = fontObj->Dict()->Get("Encoding", &iPdf); if (!enc || !enc->Dict()) return 0; const IpePdfObj *diff = enc->Dict()->Get("Differences", &iPdf); if (!diff || !diff->Array()) return 0; for (int i = 0; i < 0x100; ++i) font.iEncoding[i] = ".notdef"; int idx = 0; for (int i = 0; i < diff->Array()->Count(); ++i) { const IpePdfObj *obj = diff->Array()->Obj(i, 0); if (obj->Number()) idx = int(obj->Number()->Value()); else if (obj->Name() && idx < 0x100) font.iEncoding[idx++] = obj->Name()->Value(); } } // Get name const IpePdfObj *name = fontObj->Dict()->Get("BaseFont", 0); if (!name || !name->Name()) return false; font.iName = name->Name()->Value(); // Get widths const IpePdfObj *fc = fontObj->Dict()->Get("FirstChar", 0); const IpePdfObj *wid = fontObj->Dict()->Get("Widths", &iPdf); if (font.iType == IpeFont::EType1 && fc == 0 && wid == 0) { font.iStandardFont = true; return true; } font.iStandardFont = false; if (!fc || !fc->Number() || !wid || !wid->Array()) return false; int firstChar = int(fc->Number()->Value()); for (int i = 0; i < wid->Array()->Count(); ++i) { const IpePdfObj *obj = wid->Array()->Obj(i, 0); if (!obj->Number()) return false; font.iWidth[firstChar + i] = int(obj->Number()->Value()); } #if 0 ipeDebug("Font resource /F%d = %s", fno, font.iFontDict.CString()); #endif // get font descriptor /* /Ascent 694 /CapHeight 683 /Descent -194 /FontName /YEHLEP+CMR10 /ItalicAngle 0 /StemV 69 /XHeight 431 /FontBBox [-251 -250 1009 969] /Flags 4 /CharSet (/Sigma/one) /FontFile 8 0 R */ const IpePdfObj *fontDescriptor = fontObj->Dict()->Get("FontDescriptor", &iPdf); if (!fontDescriptor && font.iStandardFont) return true; // it's one of the 14 base fonts, no more data needed if (!fontDescriptor->Dict()) return false; const IpePdfObj *fontFile = 0; IpeString fontFileKey; for (int i = 0; i < fontDescriptor->Dict()->Count(); i++) { IpeString key(fontDescriptor->Dict()->Key(i)); if (key.size() >= 8 && key.substr(0, 8) == "FontFile") { fontFileKey = key; fontFile = fontDescriptor->Dict()->Get(key, &iPdf); } else { const IpePdfObj *data = fontDescriptor->Dict()->Get(key, &iPdf); font.iFontDescriptor += IpeString("/") + key + " " + data->Repr() + "\n"; } } font.iFontDescriptor += IpeString("/") + fontFileKey + " "; #if 0 ipeDebug("Font descriptor /F%d = %s", fno, font.iFontDescriptor.CString()); #endif // get embedded font file if (!fontFile || !fontFile->Dict() || fontFile->Dict()->Stream().size() == 0) return false; assert(font.iStreamData.size() == 0); assert(!fontFile->Dict()->Deflated()); font.iStreamData = fontFile->Dict()->Stream(); font.iLength1 = font.iLength2 = font.iLength3 = 0; for (int i = 0; i < fontFile->Dict()->Count(); i++) { IpeString key = fontFile->Dict()->Key(i); const IpePdfObj *data = fontFile->Dict()->Get(key, &iPdf); if (key != "Length") font.iStreamDict += IpeString("/") + key + " " + data->Repr() + "\n"; if (key == "Length1" && data->Number()) font.iLength1 = int(data->Number()->Value()); if (key == "Length2" && data->Number()) font.iLength2 = int(data->Number()->Value()); if (key == "Length3" && data->Number()) font.iLength3 = int(data->Number()->Value()); } if (font.iType == IpeFont::EType1 && (!font.iLength1 || !font.iLength2 || !font.iLength3)) return false; return true; } //! Read the PDF file created by Pdflatex. /*! Must have performed the call to Pdflatex, and pass the name of the resulting output file. */ bool IpeLatex::ReadPdf(IpeDataSource &source) { if (!iPdf.Parse(source)) { Warn("Ipe cannot parse the PDF file produced by Pdflatex."); return false; } const IpePdfDict *page1 = iPdf.Page(); const IpePdfObj *res = page1->Get("Resources", &iPdf); if (!res || !res->Dict()) return false; iFontPool = new IpeFontPool; const IpePdfObj *obj = res->Dict()->Get("XObject", &iPdf); if (!obj || !obj->Dict()) { Warn("Page 1 has no XForms."); return false; } // collect list of XObject's and their fonts for (int i = 0; i < obj->Dict()->Count(); i++) { IpeString key = obj->Dict()->Key(i); const IpePdfObj *xform = obj->Dict()->Get(key, &iPdf); if (!GetXForm(xform)) return false; } // collect all fonts std::map::iterator it; for (it = iFontObjects.begin(); it != iFontObjects.end(); ++it) { int fno = it->first; int objno = it->second; if (!GetEmbeddedFont(fno, objno)) return false; } return true; } //! Notify all text objects about their updated PDF code. /*! Returns true if successful. */ bool IpeLatex::UpdateTextObjects() { for (TextList::iterator it = iTextObjects.begin(); it != iTextObjects.end(); ++it) { XFormList::iterator xf = iXForms.begin(); unsigned long int ipeid = (unsigned long int) it->iText; while (xf != iXForms.end() && (*xf)->iRefCount != ipeid) ++xf; if (xf == iXForms.end()) return false; IpeText::XForm *xform = *xf; iXForms.erase(xf); it->iText->SetXForm(xform); } return true; } /*! Messages about the (mis)behaviour of Pdflatex, probably incomprehensible to the user. */ void IpeLatex::Warn(IpeString msg) { ipeDebug(msg.CString()); } // --------------------------------------------------------------------