// -------------------------------------------------------------------- // Rendering fonts onto the canvas // Using some code from Xpdf, copyright 2001-2002 Glyph & Cog, LLC // -------------------------------------------------------------------- /* 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 "ipefonts.h" #include "ipefontpool.h" #include "ipepdfparser.h" #include #include #include #include #include #include #include FT_FREETYPE_H extern bool getStdFontWidths(IpeString fontName, const IpeString encoding[0x100], int width[0x100]); // -------------------------------------------------------------------- struct Engine { Engine(); ~Engine(); bool iOk; bool iReported; FT_Library iLib; }; // Auto-constructed and destructed Freetype engine. static Engine engine; // -------------------------------------------------------------------- Engine::Engine() { iOk = false; iReported = false; if (FT_Init_FreeType(&iLib)) return; iOk = true; } Engine::~Engine() { if (iOk) FT_Done_FreeType(iLib); } // -------------------------------------------------------------------- /*! \class IpeCanvasFonts \brief Provides the fonts used to render text. */ IpeCanvasFonts *IpeCanvasFonts::New(bool aa, const IpeFontPool *fontPool, IpeFontsServices *services) { if (!engine.iOk) return 0; if (!engine.iReported) { engine.iReported = true; #if FREETYPE_MINOR >= 1 int major, minor, patch; FT_Library_Version(engine.iLib, &major, &minor, &patch); ipeDebug("Using Freetype version %d.%d.%d", major, minor, patch); if (major != FREETYPE_MAJOR || minor != FREETYPE_MINOR || patch != FREETYPE_PATCH) { QMessageBox::critical (0, "Ipe", QObject::tr("Ipe has been compiled using the header files of " "Freetype version %1.%2.%3, but is linked with Freetype " "library version %4.%5.%6. No text display is possible.") .arg(FREETYPE_MAJOR).arg(FREETYPE_MINOR).arg(FREETYPE_PATCH) .arg(major).arg(minor).arg(patch), QObject::tr("Dismiss")); FT_Done_FreeType(engine.iLib); engine.iOk = false; return 0; } #endif } return new IpeCanvasFonts(aa, fontPool, services); } //! Private constructor IpeCanvasFonts::IpeCanvasFonts(bool antiAlias, const IpeFontPool *fontPool, IpeFontsServices *services) : iAntiAlias(antiAlias), iFontPool(fontPool), iServices(services) { // nothing } IpeCanvasFonts::~IpeCanvasFonts() { for (FontSeq::iterator it = iFonts.begin(); it != iFonts.end(); ++it) delete *it; for (FaceSeq::iterator it = iFaces.begin(); it != iFaces.end(); ++it) delete *it; } //! Get a typeface. /*! Corresponds to a Freetype "face", or a PDF font resource. An IpeFace can be loaded at various sizes (transformations), resulting in individual IpeFaceSize's. */ IpeFace *IpeCanvasFonts::GetFace(int id) { for (FaceSeq::iterator it = iFaces.begin(); it != iFaces.end(); ++it) { if ((*it)->Matches(id)) return *it; } // need to create it std::vector::const_iterator it = iFontPool->begin(); while (it != iFontPool->end() && it->iLatexNumber != id) ++it; if (it == iFontPool->end()) return 0; IpeFace *face = new IpeFace(id, *it, iAntiAlias, iServices); iFaces.push_back(face); return face; } //! Get a font. IpeFaceSize *IpeCanvasFonts::GetSize(int id, const IpeLinear &matrix) { // is it in the cache? for (FontSeq::iterator it = iFonts.begin(); it != iFonts.end(); ++it) { if ((*it)->Matches(id, matrix)) { // Reinsert it at front IpeFaceSize *font = *it; iFonts.erase(it); iFonts.push_front(font); return font; } } // get the face IpeFace *face = GetFace(id); if (!face) { ipeDebug("Failed to GetFace(%d)", id); return 0; } IpeFaceSize *font = new IpeFaceSize(face, matrix); if (!font) return 0; #if 0 // avoid cache becoming too big if (iFonts.size() >= xOutFontCacheSize) { delete iFonts.back(); iFonts.pop_back(); } #endif // insert font in cache iFonts.push_front(font); return font; } // -------------------------------------------------------------------- IpeFace::IpeFace(int id, const IpeFont &font, bool antiAlias, IpeFontsServices *services) { ipeDebug("Loading face '%s'", font.iName.CString()); iId = id; iAntiAlias = antiAlias; iData = font.iStreamData; iFace = 0; // must initialize! iType = font.iType; for (int i = 0; i < 0x100; ++i) { iGlyphIndex[i] = 0; iWidth[i] = 0; } if (!font.iStandardFont) { for (int i = 0; i < 0x100; ++i) iWidth[i] = font.iWidth[i]; if (FT_New_Memory_Face(engine.iLib, (const uchar *) iData.data(), iData.size(), 0, &iFace)) return; } else { if (!getStdFontWidths(font.iName, font.iEncoding, iWidth)) return; IpeBuffer data = services->StandardFont(font.iName); if (data.size() == 0 || FT_New_Memory_Face(engine.iLib, (const uchar *) data.data(), data.size(), 0, &iFace)) return; } if (iType == IpeFont::EType1) { for (int i = 0; i < 0x100; ++i) iGlyphIndex[i] = FT_Get_Name_Index(iFace, const_cast(font.iEncoding[i].CString())); } else { // Truetype font, don't use /Encoding (shouldn't have one) FT_Set_Charmap(iFace, iFace->charmaps[0]); if (iFace->charmaps[0]->platform_id != 1 || iFace->charmaps[0]->encoding_id != 0) { ipeDebug("TrueType face %d has strange first charmap (of %d)", iId, iFace->num_charmaps); for (int i = 0; i < iFace->num_charmaps; ++i) { ipeDebug("Map %d has platform %d, encoding %d", i, iFace->charmaps[i]->platform_id, iFace->charmaps[i]->encoding_id); } } } } IpeFace::~IpeFace() { if (iFace) FT_Done_Face(iFace); } // -------------------------------------------------------------------- IpeFaceSize::IpeFaceSize(IpeFace *face, const IpeLinear &matrix) : iFace(face), iMatrix(matrix) { #if 0 ipeDebug("Creating IpeFaceSize %d, m = %g %g %g %g", face->iId, matrix.iA[0], matrix.iA[1], matrix.iA[2], matrix.iA[3]); #endif iData = 0; FT_Face fface = iFace->iFace; if (!fface) return; // transform the four corners of the font bounding box -- the min // and max values form the bounding box of the transformed font IpeVector v[4]; v[0] = IpeVector(fface->bbox.xMin, fface->bbox.yMin); v[1] = IpeVector(fface->bbox.xMin, fface->bbox.yMax); v[2] = IpeVector(fface->bbox.xMax, fface->bbox.yMin); v[3] = IpeVector(fface->bbox.xMax, fface->bbox.yMax); IpeRect bb; for (int i = 0; i < 4; ++i) { IpeVector p(matrix * v[i]); bb.AddPoint(p); } double div = fface->bbox.xMax > 20000 ? 65536 : 1; int xMin = int(bb.Min().iX / (div * fface->units_per_EM)); int xMax = int(bb.Max().iX / (div * fface->units_per_EM)); int yMin = int(bb.Min().iY / (div * fface->units_per_EM)); int yMax = int(bb.Max().iY / (div * fface->units_per_EM)); #if 0 ipeDebug("Computed bounding box [%d, %d] x [%d, %d]", xMin, xMax, yMin, yMax); #endif // this should be (max - min + 1), but we add some padding to // deal with rounding errors iGlyphW = xMax - xMin + 3; iGlyphH = yMax - yMin + 3; if (iFace->iAntiAlias) iGlyphSize = iGlyphW * iGlyphH; else iGlyphSize = ((iGlyphW + 7) >> 3) * iGlyphH; iData = new uchar[iGlyphSize]; } IpeFaceSize::~IpeFaceSize() { delete [] iData; } //! Retrieve rasterized glyph from cache, creating it if necessary. /*! \a w and \a h are set to width and height of the resulting pixmap, \a x and \a y are set to the offset in the pixmap---that is, they have to be subtracted from the hotspot to get the coordinates of the top-left pixel to be transferred. */ uchar *IpeFaceSize::GetGlyph(int ch, int &x, int &y, int &w, int &h) { if (!iFace->iFace) // face is not available return 0; double size = (iMatrix * IpeVector(0,1)).Len(); // compute the transform matrix FT_Matrix fm; fm.xx = (FT_Fixed)((iMatrix.iA[0] / size) * 65536); fm.yx = (FT_Fixed)((-iMatrix.iA[1] / size) * 65536); fm.xy = (FT_Fixed)((iMatrix.iA[2] / size) * 65536); fm.yy = (FT_Fixed)((-iMatrix.iA[3] / size) * 65536); if (FT_Set_Pixel_Sizes(iFace->iFace, 0, int(size))) return 0; FT_Set_Transform(iFace->iFace, &fm, 0); int idx = 0; if (iFace->Type() == IpeFont::EType1) idx = iFace->iGlyphIndex[ch]; else idx = FT_Get_Char_Index(iFace->iFace, (FT_ULong) ch); if (idx == 0) return 0; // if we have the FT2 bytecode interpreter, autohinting won't be used #ifdef TT_CONFIG_OPTION_BYTECODE_INTERPRETER if (FT_Load_Glyph(iFace->iFace, idx, FT_LOAD_DEFAULT)) return false; #else // XPDF comment: // FT2's autohinting doesn't always work very well (especially with // font subsets), so turn it off if anti-aliasing is enabled; if // anti-aliasing is disabled, this seems to be a tossup - some fonts // look better with hinting, some without, so leave hinting on if (FT_Load_Glyph(iFace->iFace, idx, iFace->iAntiAlias ? FT_LOAD_NO_HINTING : FT_LOAD_DEFAULT)) return 0; #endif FT_GlyphSlot slot = iFace->iFace->glyph; if (FT_Render_Glyph(slot, iFace->iAntiAlias ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO)) return 0; x = -slot->bitmap_left; y = slot->bitmap_top; w = slot->bitmap.width; h = slot->bitmap.rows; if (w > iGlyphW || h > iGlyphH) { ipeDebug("Weird FreeType glyph size: %d > %d or %d > %d\n", w, iGlyphW, h, iGlyphH); return 0; } int rowSize = iFace->iAntiAlias ? w : (w + 7) >> 3; int pitch = slot->bitmap.pitch; if (pitch < 0) pitch = -pitch; uchar *dst = iData; uchar *src = slot->bitmap.buffer; for (int y = 0; y < h; ++y) { memcpy(dst, src, rowSize); dst += rowSize; src += pitch; } return iData; } // --------------------------------------------------------------------