/** @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 <ostream>
#include <stdexcept>
#include <pango/pango.h>
#include <ctype.h>
#include <algorithm>
#include <cmath>
#include <LASi.h>
#include "contextMgr.h"
#include "glyphMgr.h"
#include "util.h"
#include "memory.h"
#include "stringDimensions.h"
#include <iomanip>
#include <stdlib.h>
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;i<randomNameLength;i++) glyph_name[i]= 65+(int)(26.0*rand()/(RAND_MAX+1.0));
glyph_name[randomNameLength]='\0';
}else{
// Get the glyph name from the font when present:
FT_Get_Glyph_Name(face, glyph_index, glyph_name, sizeof glyph_name);
}
return string(glyph_name);
}
PostscriptDocument::GlyphId::GlyphId(FT_Face face, const FT_UInt index)
{
const std::string glyphName(nameof(face, index));
const std::string faceName(face->family_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()
: _pContextMgr(new ContextMgr()), _fontSize(10), _osBody(*this),
_osFooter(*this)
{}
PostscriptDocument::~PostscriptDocument()
{
delete _pContextMgr;
}
inline PangoContext* PostscriptDocument::pangoContext() const
{
return static_cast<PangoContext*>(*_pContextMgr);
}
void PostscriptDocument::setFont(
const char* const family,
LASi::FontStyle style,
LASi::FontWeight weight,
LASi::FontVariant variant,
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<PangoContext*>(*_pContextMgr), font_description);
}
void PostscriptDocument::for_each_glyph_do(const string& s, const GLYPH_FUNC func, void* contextData,
bool applyOffset)
{
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<PangoItem*>(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<FT_Glyph>(glyphMgr)) { // if glyph is not in map
//
// access glyph from font face and put it in map
//
FT_Glyph glyph;
//
// DEBUG:
//
//std::cerr << "Glyph Index: " << glyph_index << std::endl;
FT_Error error = FT_Load_Glyph(face,glyph_index,FT_LOAD_NO_BITMAP);
if(error){
//
//DEBUG:
//
//std::cerr << "PANGO is returning a glyph index of " << std::hex << glyph_index << std::endl;
//std::cerr << "but PANGO_GLYPH_UNKNOWN_FLAG is supposed to be: " << 0x10000000 << std::endl;
//std::cerr << "and PANGO_GLYPH_EMPTY is supposed to be: " << 0x0FFFFFFF << std::endl;
//
// Substitute something that works: All fonts are supposed
// to handle glyph_index 0 as the default replacement glyph:
//
evalReturnCode(FT_Load_Glyph(face,0,FT_LOAD_NO_BITMAP),"FT_Load_Glyph");
}else{
evalReturnCode(FT_Load_Glyph(face, glyph_index,FT_LOAD_NO_BITMAP), "FT_Load_Glyph");
}
evalReturnCode(FT_Get_Glyph(face->glyph, &glyph), "FT_Get_Glyph");
glyphMgr.assign(glyph);
}
ostream* pos = 0;
double x_rmove = 0, y_rmove = 0;
if (applyOffset) {
const PangoGlyphGeometry& geo = pGlyphInfo[i].geometry;
if (geo.x_offset != 0 || geo.y_offset != 0) {
const double scale = _fontSize / (DRAWING_SCALE * PANGO_SCALE);
pos = reinterpret_cast<ostream*>(contextData);
x_rmove = scale * geo.x_offset;
y_rmove = scale * geo.y_offset;
(*pos) << x_rmove << ' ' << y_rmove << " rmoveto" << endl;
}
}
//
// glyph is guaranteed to be in map:
// Call the function that operates on the glyph:
//
(this->*func)(*_glyphMap.find(glyphId), contextData);
if (applyOffset && pos) { // undo previous rmoveto
(*pos) << -x_rmove << ' ' << -y_rmove << " rmoveto" << endl;
}
}
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<GlyphId, FreetypeGlyphMgr>
* @param contextData std::pair<double, double>, the x and y dimensions
*/
void PostscriptDocument::accrue_dimensions(
const PostscriptDocument::GlyphMap::value_type& mapval, void* contextData)
{
// const GlyphId& gid = static_cast<GlyphId>(mapval.first);
const FreetypeGlyphMgr& glyphMgr = static_cast<FreetypeGlyphMgr>(mapval.second);
const FT_Glyph& glyph = static_cast<FT_Glyph>(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<StringDimensions *>(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<GlyphId>(mapval.first);
ostream* pos = reinterpret_cast<ostream*>(contextData);
static_cast<ostream&>(*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, true);
}
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 LGPL." << 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<PangoContext*>(*_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;
}