/** @file * libLASi provides a C++ output stream interface for writing * Postscript documents containing text strings in any of the world's * scripts supported by Unicode 4.0 and Pango. * Copyright (C) 2003, 2004, 2006 by Larry Siden. * See README file in project root directory for copyright and contact info. * See COPYING file in project root for terms of re-distribution. */ #include #include #include #include #include #include #include #include "contextMgr.h" #include "glyphMgr.h" #include "util.h" #include "memory.h" #include "stringDimensions.h" #include #include using namespace std; using namespace LASi; static string nameof(const FT_Face& face, const FT_UInt glyph_index) { const int N = 128; // Length of buffer to hold glyph name const int randomNameLength = 16; // Length of a random glyph name char glyph_name[N]; if (!FT_HAS_GLYPH_NAMES(face)){ // // 2004.12.03.ET FIX: Since the glyph has no name entry, // arbitrarily generate a string consisting of a random // assortment of ASCII capital letters to use: // for(int i=0;ifamily_name); const std::string& variant(face->style_name); ostringstream os; os << glyphName << '-' << faceName << '-' << variant << '-' << index; _str = os.str(); const int len = _str.size(); //cerr << "PostscriptDocument::GlyphId::GlyphId(...) before _str=" << _str << endl; // replace spaces with '-' for (int i=0 ; i < len ; ++i) { if (isspace(_str[i])) _str.replace(i, 1, 1, '-'); } //cerr << "PostscriptDocument::GlyphId::GlyphId(...) _str=" << _str << endl; } PostscriptDocument::PostscriptDocument() : _fontSize(10), _osBody(*this), _osFooter(*this), _pContextMgr(new ContextMgr()) {} PostscriptDocument::~PostscriptDocument() { delete _pContextMgr; } inline PangoContext* PostscriptDocument::pangoContext() const { return static_cast(*_pContextMgr); } void PostscriptDocument::setFont( const char* const family, const LASi::FontStyle style, const LASi::FontWeight weight, const LASi::FontVariant variant, const LASi::FontStretch stretch) { // // Style: // PangoStyle _style; switch(style){ case NORMAL_STYLE: _style=PANGO_STYLE_NORMAL; break; case ITALIC: _style=PANGO_STYLE_ITALIC; break; case OBLIQUE: _style=PANGO_STYLE_OBLIQUE; break; default: _style=PANGO_STYLE_NORMAL; break; } // // Weight: // PangoWeight _weight; switch(weight){ case NORMAL_WEIGHT: _weight=PANGO_WEIGHT_NORMAL; break; case BOLD: _weight=PANGO_WEIGHT_BOLD; break; case ULTRALIGHT: _weight=PANGO_WEIGHT_ULTRALIGHT; break; case LIGHT: _weight=PANGO_WEIGHT_LIGHT; break; case ULTRABOLD: _weight=PANGO_WEIGHT_ULTRABOLD; break; case HEAVY: _weight=PANGO_WEIGHT_HEAVY; break; default: _weight=PANGO_WEIGHT_NORMAL; break; } // // Variant: // PangoVariant _variant; switch(variant){ case NORMAL_VARIANT: _variant=PANGO_VARIANT_NORMAL; break; case SMALLCAPS: _variant=PANGO_VARIANT_SMALL_CAPS; break; default: _variant=PANGO_VARIANT_NORMAL; break; } // // Stretch: // PangoStretch _stretch; switch(stretch){ case NORMAL_STRETCH: _stretch=PANGO_STRETCH_NORMAL; break; case ULTRACONDENSED: _stretch=PANGO_STRETCH_ULTRA_CONDENSED; break; case EXTRACONDENSED: _stretch=PANGO_STRETCH_EXTRA_CONDENSED; break; case CONDENSED: _stretch=PANGO_STRETCH_CONDENSED; break; case SEMICONDENSED: _stretch=PANGO_STRETCH_SEMI_CONDENSED; break; case SEMIEXPANDED: _stretch=PANGO_STRETCH_SEMI_EXPANDED; break; case EXPANDED: _stretch=PANGO_STRETCH_EXPANDED; break; case EXTRAEXPANDED: _stretch=PANGO_STRETCH_EXTRA_EXPANDED; break; case ULTRAEXPANDED: _stretch=PANGO_STRETCH_ULTRA_EXPANDED; break; default: _stretch=PANGO_STRETCH_NORMAL; break; } PangoFontDescription* font_description = pango_font_description_new(); pango_font_description_set_family (font_description, family); pango_font_description_set_style (font_description, _style); pango_font_description_set_weight (font_description, _weight); pango_font_description_set_variant (font_description, _variant); pango_font_description_set_stretch (font_description, _stretch); pango_font_description_set_size(font_description, DRAWING_SCALE*PANGO_SCALE); pango_context_set_font_description (static_cast(*_pContextMgr), font_description); } void PostscriptDocument::for_each_glyph_do(const string& s, const GLYPH_FUNC func, void* contextData) { PangoAttrList* const attrList = pango_attr_list_new(); // needed only for call to pango_itemize() GList* glItems = pango_itemize( pangoContext(), s.c_str(), 0, s.length(), attrList, (PangoAttrIterator *) 0); pango_attr_list_unref(attrList); for (; glItems ; glItems = g_list_next(glItems)) { PangoItem* const pItem = reinterpret_cast(glItems->data); PangoGlyphString* const pGlyphString = pango_glyph_string_new(); pango_shape(s.c_str() + pItem->offset, pItem->length, &pItem->analysis, pGlyphString); const FT_Face face = pango_ft2_font_get_face(pItem->analysis.font); PangoGlyphInfo* const pGlyphInfo = pGlyphString->glyphs; for (int i=0 ; i < pGlyphString->num_glyphs ; ++i) { const FT_UInt glyph_index = pGlyphInfo[i].glyph; // get glyph index const PostscriptDocument::GlyphId glyphId(face, glyph_index); // construct GlyphId FreetypeGlyphMgr& glyphMgr = _glyphMap[glyphId]; // access glyph from map if (0 == static_cast(glyphMgr)) { // if glyph is not in map // access glyph from font face and put it in map FT_Glyph glyph; evalReturnCode(FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT), "FT_Load_Glyph"); evalReturnCode(FT_Get_Glyph(face->glyph, &glyph), "FT_Get_Glyph"); glyphMgr.assign(glyph); } // // glyph is guaranteed to be in map: // Call the function that operates on the glyph: // (this->*func)(*_glyphMap.find(glyphId), contextData); } pango_glyph_string_free(pGlyphString); pango_item_free(pItem); } g_list_free(glItems); } /** Add the next glyphs dimensions to the bounding box (contextData). * If the advance is in the x direction (the usual case), * the box grows in the x direction, yMax becomes the height * of the tallest character, and yMin the descent of the most * descending character. * * @param mapval std::pair * @param contextData std::pair, the x and y dimensions */ void PostscriptDocument::accrue_dimensions( const PostscriptDocument::GlyphMap::value_type& mapval, void* contextData) { // const GlyphId& gid = static_cast(mapval.first); const FreetypeGlyphMgr& glyphMgr = static_cast(mapval.second); const FT_Glyph& glyph = static_cast(glyphMgr); // // // //const double y_adv = std::abs(glyph->advance.y / (double) 0x10000); // const double x_adv = std::abs(glyph->advance.x / (double) 0x10000); // convert from 16.16 format // // // FT_BBox bbox; FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_unscaled, &bbox); // // Get the mins and maxes, converting from 26.6 format: // // const double x_min = bbox.xMin/64.0; // const double x_max = bbox.xMax/64.0; // const double y_min = bbox.yMin/64.0; const double y_max = bbox.yMax/64.0; StringDimensions *SD = reinterpret_cast(contextData); SD->accrueXAdvance(x_adv); SD->setYMin(y_min); SD->setYMax(y_max); } /** Insert a Postscript glyph_routine call into output stream (contextData). */ void PostscriptDocument::invoke_glyph_routine( const PostscriptDocument::GlyphMap::value_type& mapval, void* contextData) { const GlyphId& gid = static_cast(mapval.first); ostream* pos = reinterpret_cast(contextData); static_cast(*pos) << this->getFontSize() << " " << gid.str() << endl; } /** Returns the line spacing, x-advance, y-minimum and y-maximum * based on the current font face and font size. A bounding box * around the text string, s, can be constructed from the xAdvance, * yMinimum, and yMaximum. yMinimum tells you the descent from the * baseline. yMaximum tells you the ascent from the baseline. * The line spacing provides an inter-line spacing for multi-line * text layout. * * This version accepts a const C-style character string. * */ void PostscriptDocument::get_dimensions(const char* s, double *lineSpacing, double *xAdvance, double *yMin, double *yMax) { StringDimensions SD; for_each_glyph_do(s, &PostscriptDocument::accrue_dimensions,&SD); const double scale = _fontSize / DRAWING_SCALE; // // We always want to retrieve at least the lineSpacing: // *lineSpacing = SD.getLineSpacing() * scale; // // But xAdvance, yMin, and yMax are only necessary to retrieve // if we want to have the bounding box, so we allow default // parameters set to NULL: // if(xAdvance!=NULL) *xAdvance = SD.getXAdvance() * scale; if(yMin !=NULL) *yMin = SD.getYMin() * scale; if(yMax !=NULL) *yMax = SD.getYMax() * scale; } /** Returns the line spacing, x-advance, y-minimum and y-maximum * based on the current font face and font size. A bounding box * around the text string, s, can be constructed from the xAdvance, * yMinimum, and yMaximum. yMinimum tells you the descent from the * baseline. yMaximum tells you the ascent from the baseline. * The line spacing provides an inter-line spacing for multi-line * text layout. * * This version accepts an STL standard string class string. * */ void PostscriptDocument::get_dimensions(std::string s, double *lineSpacing, double *xAdvance, double *yMin, double *yMax){ get_dimensions(s.c_str(),lineSpacing,xAdvance,yMin,yMax); } void show::apply(oPostscriptStream& os) const { //cerr << "show::apply(os): _str = " << _str << endl; PostscriptDocument& doc = os.doc(); doc.for_each_glyph_do(_str, &PostscriptDocument::invoke_glyph_routine, &os); } const unsigned int PostscriptDocument::DRAWING_SCALE = PANGO_SCALE; /** * Writes out the document. * */ void PostscriptDocument::write(std::ostream& os, double llx, double lly, double urx, double ury) { // // Output document header: // // // If any of the bounding box parameters are non-zero, // then write out an EPS document with a bounding box: // if(llx || lly || urx || ury){ // // Encapsulated PostScript Header: // os << "%!PS-Adobe-3.0 EPSF-3.0" << endl; os << "%%BoundingBox: " << int(llx) << " " << int(lly) << " " << int(urx) << " " << int(ury) << endl; os << "%%HiResBoundingBox: " << std::setprecision(9) << llx << " " << lly << " " << urx << " " << ury << endl; }else{ // // Normal Postscript header for print media: // os << "%!PS-Adobe-3.0" << endl; } // // Rest of header: // os << "%%Creator: libLASi C++ Stream Interface for Postscript. LASi is hosted on http://www.unifont.org." << endl; os << "%%Copyright: (c) 2003, 2004, 2006 by Larry Siden. All Rights Reserved. Released under the GPL." << endl; os << endl; os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl; os << "%" << endl; os << "% START Document Header:" << endl; os << "%" << endl; os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl; // // If a "%!PS" is found at the beginning of the user's header, // warn them that LASi already provides that: // if( _osHeader.str().find("%!PS")!=string::npos) cerr << "WARNING: LASi automatically provides \"%!PS-Adobe-3.0\" at the start of the document!" << endl; // // Make sure there is a "%%BeginProlog" to complement the "%%EndProlog" that // LASi puts after the end of the glyph routines: // if( _osHeader.str().find("%%BeginProlog")==string::npos) os << "%%BeginProlog" << endl; os << _osHeader.str() << endl; // // Write out the glyph routines: // os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl; os << "%" << endl; os << "% START LASi Glyph Routines:" << endl; os << "%" << endl; os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl; os << "%%BeginResource: GlyphRoutines" << endl; for_each(_glyphMap.begin(), _glyphMap.end(), write_glyph_routine_to_stream(os, static_cast(*_pContextMgr))); os << "%%EndResource" << endl; os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl; os << "%" << endl; os << "% END LASi Glyph Routines:" << endl; os << "%" << endl; os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl; os << "%%EndProlog" << endl; os << endl; os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl; os << "%" << endl; os << "% START Document Body:" << endl; os << "%" << endl; os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl; os << _osBody.str() << endl; os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl; os << "%" << endl; os << "% END Document Body:" << endl; os << "%" << endl; os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl; os << "%" << endl; os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl; os << "%" << endl; os << "% START Document Footer:" << endl; os << "%" << endl; os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl; os << "%%Trailer" << endl; os << _osFooter.str() << endl; os << "%%EOF" << endl; }