// -------------------------------------------------------------------- // Creating Postscript output // -------------------------------------------------------------------- /* 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 "ipeimage.h" #include "ipetext.h" #include "ipevisitor.h" #include "ipepainter.h" #include "ipegroup.h" #include "iperef.h" #include "ipeutils.h" #include "ipedoc.h" #include "ipepswriter.h" #include "ipefontpool.h" static const char hexChar[17] = "0123456789abcdef"; // -------------------------------------------------------------------- class IpePsPainter : public IpePainter { public: IpePsPainter(const IpeStyleSheet *style, IpeStream &stream); virtual ~IpePsPainter() { } virtual void Rect(const IpeRect &re); protected: virtual void DoPush(); virtual void DoPop(); virtual void DoNewPath(); virtual void DoMoveTo(const IpeVector &v); virtual void DoLineTo(const IpeVector &v); virtual void DoCurveTo(const IpeVector &v1, const IpeVector &v2, const IpeVector &v3); virtual void DoClosePath(); virtual void DoDrawPath(); virtual void DoDrawBitmap(IpeBitmap bitmap); virtual void DoDrawText(const IpeText *text); private: void DrawColor(IpeAttribute color); void StrokePath(); void FillPath(bool eoFill, bool preservePath); private: IpeStream &iStream; // iActiveState records the attribute settings that have been // recorded in the Postscript output, to avoid repeating them // over and over again. StateSeq iActiveState; int iImageNumber; }; IpePsPainter::IpePsPainter(const IpeStyleSheet *style, IpeStream &stream) : IpePainter(style), iStream(stream) { State state; // Postscript has only one current color: // we use iStroke for that state.iStroke = IpeAttribute::Black(); state.iDashStyle = IpeAttribute::Solid(); state.iLineCap = IpeAttribute(IpeAttribute::ELineCap, false, 0); state.iLineJoin = IpeAttribute(IpeAttribute::ELineJoin, false, 0); iActiveState.push_back(state); iImageNumber = 1; } void IpePsPainter::Rect(const IpeRect &re) { iStream << Matrix() * re.Min() << " " << Matrix().Linear() * (re.Max() - re.Min()) << " re\n"; } void IpePsPainter::DoNewPath() { iStream << "np\n"; } void IpePsPainter::DoMoveTo(const IpeVector &v) { iStream << Matrix() * v << " m\n"; } void IpePsPainter::DoLineTo(const IpeVector &v) { iStream << Matrix() * v << " l\n"; } void IpePsPainter::DoCurveTo(const IpeVector &v1, const IpeVector &v2, const IpeVector &v3) { iStream << Matrix() * v1 << " " << Matrix() * v2 << " " << Matrix() * v3 << " c\n"; } void IpePsPainter::DoClosePath() { iStream << "h "; } void IpePsPainter::DoPush() { State state = iActiveState.back(); iActiveState.push_back(state); // don't need the outermost push/pop // (it's only to preserve outer transformation for "Untransform" // if (iState.size() > 2) // have already pushed iStream << "q "; } void IpePsPainter::DoPop() { iActiveState.pop_back(); //if (iState.size() > 1) // have already popped iStream << "Q\n"; } void IpePsPainter::DrawColor(IpeAttribute color) { if (color.IsNullOrVoid()) return; assert(color.IsAbsolute()); IpeColor col = StyleSheet()->Repository()->ToColor(color); if (col.IsGray()) iStream << col.iRed << " g "; else iStream << col << " rg "; } void IpePsPainter::StrokePath() { State &s = iState.back(); State &sa = iActiveState.back(); const IpeRepository *rep = StyleSheet()->Repository(); if (s.iDashStyle && s.iDashStyle != sa.iDashStyle) { sa.iDashStyle = s.iDashStyle; if (s.iDashStyle.IsSolid()) iStream << "[] 0 d "; else iStream << rep->String(s.iDashStyle) << " d "; } if (s.iLineWidth && s.iLineWidth != sa.iLineWidth) { sa.iLineWidth = s.iLineWidth; iStream << rep->String(s.iLineWidth) << " w "; } if (s.iLineCap && s.iLineCap != sa.iLineCap) { sa.iLineCap = s.iLineCap; iStream << int(s.iLineCap.Index()) << " J "; } if (s.iLineJoin && s.iLineJoin != sa.iLineJoin) { sa.iLineJoin = s.iLineJoin; iStream << int(s.iLineJoin.Index()) << " j "; } if (s.iStroke != sa.iStroke) { sa.iStroke = s.iStroke; DrawColor(s.iStroke); } iStream << "S"; } void IpePsPainter::FillPath(bool eoFill, bool preservePath) { State &s = iState.back(); State &sa = iActiveState.back(); if (s.iFill != sa.iStroke) { sa.iStroke = s.iFill; DrawColor(s.iFill); } if (preservePath) iStream << "q "; iStream << (eoFill ? "f*" : "f"); if (preservePath) iStream << " Q "; } void IpePsPainter::DoDrawPath() { bool noStroke = Stroke().IsNullOrVoid(); bool noFill = Fill().IsNullOrVoid(); bool eoFill = !WindRule() || !WindRule().Index(); if (noStroke && noFill) { iStream << "np"; // flush path } else if (noStroke) { FillPath(eoFill, false); } else if (noFill) { StrokePath(); } else { FillPath(eoFill, true); StrokePath(); } iStream << "\n"; } void IpePsPainter::DoDrawBitmap(IpeBitmap bitmap) { switch (bitmap.ColorSpace()) { case IpeBitmap::EDeviceGray: iStream << "/DeviceGray setcolorspace "; break; case IpeBitmap::EDeviceRGB: iStream << "/DeviceRGB setcolorspace "; break; case IpeBitmap::EDeviceCMYK: iStream << "/DeviceCMYK setcolorspace "; break; } iStream << Matrix() << " cm\n"; iStream << "<< /ImageType 1\n"; iStream << " /Width " << bitmap.Width() << "\n"; iStream << " /Height " << bitmap.Height() << "\n"; iStream << " /BitsPerComponent " << bitmap.BitsPerComponent() << "\n"; iStream << " /Decode [ "; for (int i = 0; i < bitmap.Components(); ++i) iStream << "0 1 "; iStream << "]\n"; iStream << " /ImageMatrix [ " << bitmap.Width() << " 0 0 " << -bitmap.Height() << " 0 " << bitmap.Height() << " ]\n"; iStream << " /DataSource currentfile /ASCII85Decode filter"; if (bitmap.Filter() == IpeBitmap::EFlateDecode) iStream << " /FlateDecode filter\n"; else if (bitmap.Filter() == IpeBitmap::EDCTDecode) iStream << " /DCTDecode filter\n"; else iStream << "\n"; iStream << ">>\n"; iStream << "%%BeginIpeImage: " << iImageNumber << " " << bitmap.Size() << "\n"; bitmap.SetObjNum(iImageNumber++); iStream << "image\n"; const char *p = bitmap.Data(); const char *p1 = p + bitmap.Size(); IpeA85Stream a85(iStream); while (p < p1) a85.PutChar(*p++); a85.Close(); iStream << "%%EndIpeImage\n"; } void IpePsPainter::DoDrawText(const IpeText *text) { const IpeText::XForm *xf = text->GetXForm(); if (xf) { iStream << Matrix() << " cm " ; iStream << IpeMatrix(xf->iStretch.iX, 0, 0, xf->iStretch.iY, 0, 0) << " cm "; // remove whitespace at the end const char *data = xf->iStream.data(); const char *p = data + xf->iStream.size() - 1; while (p >= data && (*p == ' ' || *p == '\n')) --p; if (p >= data) iStream.PutRaw(data, p - data + 1); iStream << "\n"; } } // -------------------------------------------------------------------- /*! \class IpePsWriter \brief Create Postscript file. This class is responsible for the creation of a Postscript file from the Ipe data. You have to create an IpePsWriter first, providing a stream that has been opened for (binary) writing and is empty. */ //! Create Postscript writer operating on this (open and empty) file. IpePsWriter::IpePsWriter(IpeTellStream &stream, const IpeDocument *doc, bool eps) : iStream(stream), iDoc(doc), iEps(eps) { iOrdinal = 1; // first page will be ordinal 1 } // -------------------------------------------------------------------- //! Create the document header and prolog (the resources). /*! Embeds no fonts if \c pool is 0. Returns false if a TrueType font is present. */ bool IpePsWriter::CreateHeader(IpeString creator) { const IpeFontPool *pool = iDoc->FontPool(); const IpeDocument::SProperties &props = iDoc->Properties(); // Check needed and supplied fonts, and reject Truetype fonts IpeString needed = ""; IpeString neededSep = "%%DocumentNeededResources: font "; IpeString supplied = ""; IpeString suppliedSep = "%%DocumentSuppliedResources: font "; if (pool) { for (IpeFontPool::const_iterator font = pool->begin(); font != pool->end(); ++font) { if (font->iType == IpeFont::ETrueType) return false; if (font->iStandardFont) { needed += neededSep; needed += font->iName; needed += "\n"; neededSep = "%%+ font "; } else { supplied += suppliedSep; supplied += font->iName; supplied += "\n"; suppliedSep = "%%+ font "; } } } if (iEps) iStream << "%!PS-Adobe-3.0 EPSF-3.0\n"; else iStream << "%!PS-Adobe-3.0\n"; iStream << "%%Creator: Ipelib " << IPELIB_VERSION; if (!creator.empty()) iStream << " (" << creator << ")"; iStream << "\n"; // CreationDate is informational, no fixed format iStream << "%%CreationDate: " << props.iModified << "\n"; // Should use level 1 if no images present? iStream << "%%LanguageLevel: 2\n"; // If bounding box is used for non-EPS files, it should // be a bounding box for all pages overlaid if (iEps) { const IpePage *page = (*iDoc)[0]; std::vector layers; page->MakeLayerTable(layers, 0, false); IpeBBoxPainter painter(iDoc->StyleSheet()); for (IpePage::const_iterator it = page->begin(); it != page->end(); ++it) { if (layers[it->Layer()]) it->Object()->Draw(painter); } IpeRect r = painter.BBox(); iStream << "%%BoundingBox: " // (x0, y0) (x1, y1) << int(r.Min().iX) << " " << int(r.Min().iY) << " " << int(r.Max().iX + 1) << " " << int(r.Max().iY + 1) << "\n"; iStream << "%%HiResBoundingBox: " << r.Min() << " " << r.Max() << "\n"; } else { IpeRect m = iDoc->Properties().iMedia; iStream << "%%DocumentMedia: Plain " << int(m.Width()) << " " << int(m.Height()) << " 0 () ()\n"; iStream << "%%Pages: " << iDoc->TotalViews() << "\n"; } iStream << needed; iStream << supplied; if (props.iAuthor.size()) iStream << "%%Author: " << props.iAuthor << "\n"; if (props.iTitle.size()) iStream << "%%Title: " << props.iTitle << "\n"; iStream << "%%EndComments\n"; iStream << "%%BeginProlog\n"; // procset // is upwards compatible, not iStream << "%%BeginResource: procset ipe 6.0 " << IPELIB_VERSION << "\n"; iStream << "/ipe 40 dict def ipe begin\n"; iStream << "/np { newpath } def\n"; iStream << "/m { moveto } def\n"; iStream << "/l { lineto } def\n"; iStream << "/c { curveto } def\n"; iStream << "/h { closepath } def\n"; iStream << "/re { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto\n"; iStream << " neg 0 rlineto closepath } def\n"; iStream << "/d { setdash } def\n"; iStream << "/w { setlinewidth } def\n"; iStream << "/J { setlinecap } def\n"; iStream << "/j { setlinejoin } def\n"; iStream << "/cm { [ 7 1 roll ] concat } def\n"; iStream << "/q { gsave } def\n"; iStream << "/Q { grestore } def\n"; iStream << "/g { setgray } def\n"; iStream << "/rg { setrgbcolor } def\n"; iStream << "/G { setgray } def\n"; // used inside text objects iStream << "/RG { setrgbcolor } def\n"; // used inside text objects iStream << "/S { stroke } def\n"; iStream << "/f* { eofill } def\n"; iStream << "/f { fill } def\n"; iStream << "/ipeMakeFont {\n"; iStream << " exch findfont\n"; iStream << " dup length dict begin\n"; iStream << " { 1 index /FID ne { def } { pop pop } ifelse } forall\n"; iStream << " /Encoding exch def\n"; iStream << " currentdict\n"; iStream << " end\n"; iStream << " definefont pop\n"; iStream << "} def\n"; iStream << "/ipeFontSize 0 def\n"; iStream << "/Tf { dup /ipeFontSize exch store selectfont } def\n"; iStream << "/Td { translate } def\n"; iStream << "/BT { gsave } def\n"; iStream << "/ET { grestore } def\n"; iStream << "/TJ { 0 0 moveto { dup type /stringtype eq\n"; iStream << " { show } { ipeFontSize mul -0.001 mul 0 rmoveto } ifelse\n"; iStream << "} forall } def\n"; iStream << "end\n"; iStream << "%%EndResource\n"; iStream << "%%EndProlog\n"; iStream << "%%BeginSetup\n"; iStream << "ipe begin\n"; if (pool) { for (IpeFontPool::const_iterator font = pool->begin(); font != pool->end(); ++font) { if (font->iStandardFont) { iStream << "%%IncludeResource: font " << font->iName << "\n"; } else { iStream << "%%BeginResource: font " << font->iName << "\n"; EmbedFont(*font); iStream << "%%EndResource\n"; } // Create font dictionary iStream << "/F" << font->iLatexNumber << " /" << font->iName; IpeString sep = "\n[ "; for (int i = 0; i < 0x100; ++i) { if (i % 8 == 0) { iStream << sep; sep = "\n "; } iStream << "/" << font->iEncoding[i]; } iStream << " ]\nipeMakeFont\n"; } } iStream << "%%EndSetup\n"; return true; } //! Destructor. IpePsWriter::~IpePsWriter() { // nothing } //! Write all fonts to the Postscript file. void IpePsWriter::EmbedFont(const IpeFont &font) { // write unencrypted front matter iStream.PutRaw(font.iStreamData.data(), font.iLength1); // write encrypted section const char *p = font.iStreamData.data() + font.iLength1; const char *p1 = font.iStreamData.data() + font.iLength1 + font.iLength2; int i = 0; while (p < p1) { iStream.PutChar(hexChar[(*p >> 4) & 0x0f]); iStream.PutChar(hexChar[*p & 0x0f]); ++p; if (++i % 32 == 0) iStream << "\n"; } if (i % 32 > 0) iStream << "\n"; // write tail matter iStream.PutRaw(p1, font.iLength3); } // -------------------------------------------------------------------- //! Create contents and page stream for this page view. void IpePsWriter::CreatePageView(int pno, int vno) { const IpePage *page = (*iDoc)[pno]; if (!iEps) { if (page->CountViews() > 1) iStream << "%%Page: " << (pno + 1) << "-" << (vno + 1) << " " << iOrdinal++ << "\n"; else iStream << "%%Page: " << (pno + 1) << " " << iOrdinal++ << "\n"; } // Create page stream IpePsPainter painter(iDoc->StyleSheet(), iStream); std::vector layers; page->MakeLayerTable(layers, vno, false); for (IpePage::const_iterator it = page->begin(); it != page->end(); ++it) { if (layers[it->Layer()]) it->Object()->Draw(painter); } iStream << "showpage\n"; } //! Save all pages in Postscript format. void IpePsWriter::CreatePages(bool lastView) { for (uint page = 0; page < iDoc->size(); ++page) { int nViews = (*iDoc)[page]->CountViews(); if (lastView) CreatePageView(page, lastView - 1); else for (int view = 0; view < nViews; ++view) CreatePageView(page, view); } } //! Create the trailer of the Postscsript file. void IpePsWriter::CreateTrailer() { iStream << "%%Trailer\n"; iStream << "end\n"; // to end ipe dictionary iStream << "%%EOF\n"; } // -------------------------------------------------------------------- class IpePercentStream : public IpeStream { public: IpePercentStream(IpeStream &stream) : iStream(stream), iNeedPercent(true) { /* nothing */ } virtual void PutChar(char ch); private: IpeStream &iStream; bool iNeedPercent; }; void IpePercentStream::PutChar(char ch) { if (iNeedPercent) iStream.PutChar('%'); iStream.PutChar(ch); iNeedPercent = (ch == '\n'); } //! Save Ipe information in XML format. void IpePsWriter::CreateXml(IpeString creator, int compressLevel) { IpePercentStream s1(iStream); if (compressLevel > 0) { iStream << "%%BeginIpeXml: /FlateDecode\n"; IpeA85Stream a85(s1); IpeDeflateStream s2(a85, compressLevel); iDoc->SaveAsXml(s2, creator, true); s2.Close(); } else { iStream << "%%BeginIpeXml:\n"; iDoc->SaveAsXml(s1, creator, true); s1.Close(); } iStream << "%%EndIpeXml\n"; } // --------------------------------------------------------------------