File indexing completed on 2024-05-12 16:06:39
0001 // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- 0002 // TeXFont_PFB.cpp 0003 // 0004 // Part of KDVI - A DVI previewer for the KDE desktop environment 0005 // 0006 // SPDX-FileCopyrightText: 2003 Stefan Kebekus 0007 // SPDX-License-Identifier: GPL-2.0-or-later 0008 0009 // This file is compiled only if the FreeType library is present on 0010 // the system 0011 0012 #include <config.h> 0013 0014 #ifdef HAVE_FREETYPE 0015 0016 #include "TeXFont_PFB.h" 0017 #include "debug_dvi.h" 0018 #include "fontpool.h" 0019 0020 #include <KLocalizedString> 0021 0022 #include <QImage> 0023 #include <QLoggingCategory> 0024 0025 //#define DEBUG_PFB 1 0026 0027 TeXFont_PFB::TeXFont_PFB(TeXFontDefinition *parent, fontEncoding *enc, double slant) 0028 : TeXFont(parent) 0029 , face(nullptr) 0030 { 0031 #ifdef DEBUG_PFB 0032 if (enc != 0) 0033 qCDebug(OkularDviDebug) << "TeXFont_PFB::TeXFont_PFB( parent=" << parent << ", encoding=" << enc->encodingFullName << " )"; 0034 else 0035 qCDebug(OkularDviDebug) << "TeXFont_PFB::TeXFont_PFB( parent=" << parent << ", encoding=0 )"; 0036 #endif 0037 0038 fatalErrorInFontLoading = false; 0039 0040 int error = FT_New_Face(parent->font_pool->FreeType_library, parent->filename.toLocal8Bit().constData(), 0, &face); 0041 0042 if (error == FT_Err_Unknown_File_Format) { 0043 errorMessage = i18n("The font file %1 could be opened and read, but its font format is unsupported.", parent->filename); 0044 qCCritical(OkularDviDebug) << errorMessage; 0045 fatalErrorInFontLoading = true; 0046 return; 0047 } else if (error) { 0048 errorMessage = i18n("The font file %1 is broken, or it could not be opened or read.", parent->filename); 0049 qCCritical(OkularDviDebug) << errorMessage; 0050 fatalErrorInFontLoading = true; 0051 return; 0052 } 0053 0054 // Take care of slanting, and transform all characters in the font, if necessary. 0055 if (slant != 0.0) { 0056 // Construct a transformation matrix for vertical shear which will 0057 // be used to transform the characters. 0058 transformationMatrix.xx = 0x10000; 0059 transformationMatrix.xy = (FT_Fixed)(slant * 0x10000); 0060 transformationMatrix.yx = 0; 0061 transformationMatrix.yy = 0x10000; 0062 0063 FT_Set_Transform(face, &transformationMatrix, nullptr); 0064 } 0065 0066 if (face->family_name != nullptr) { 0067 parent->fullFontName = QString::fromLocal8Bit(face->family_name); 0068 } 0069 0070 // Finally, we need to set up the charMap array, which maps TeX 0071 // character codes to glyph indices in the font. (Remark: the 0072 // charMap, and the font encoding procedure is necessary, because 0073 // TeX is only able to address character codes 0-255 while 0074 // e.g. Type1 fonts may contain several thousands of characters) 0075 if (enc != nullptr) { 0076 parent->fullEncodingName = enc->encodingFullName.remove(QStringLiteral("Encoding")); 0077 parent->fullEncodingName = enc->encodingFullName.remove(QStringLiteral("encoding")); 0078 0079 // An encoding vector is given for this font, i.e. an array of 0080 // character names (such as: 'parenleft' or 'dotlessj'). We use 0081 // the FreeType library function 'FT_Get_Name_Index()' to 0082 // associate glyph indices to those names. 0083 #ifdef DEBUG_PFB 0084 qCDebug(OkularDviDebug) << "Trying to associate glyph indices to names from the encoding vector."; 0085 #endif 0086 for (int i = 0; i < 256; i++) { 0087 charMap[i] = FT_Get_Name_Index(face, (FT_String *)(enc->glyphNameVector[i].toLatin1().data())); 0088 #ifdef DEBUG_PFB 0089 qCDebug(OkularDviDebug) << i << ": " << enc->glyphNameVector[i] << ", GlyphIndex=" << charMap[i]; 0090 #endif 0091 } 0092 } else { 0093 // If there is no encoding vector available, we check if the font 0094 // itself contains a charmap that could be used. An admissible 0095 // charMap will be stored under platform_id=7 and encoding_id=2. 0096 FT_CharMap found = nullptr; 0097 for (int n = 0; n < face->num_charmaps; n++) { 0098 FT_CharMap charmap = face->charmaps[n]; 0099 if (charmap->platform_id == 7 && charmap->encoding_id == 2) { 0100 found = charmap; 0101 break; 0102 } 0103 } 0104 0105 if ((found != nullptr) && (FT_Set_Charmap(face, found) == 0)) { 0106 // Feed the charMap array with the charmap data found in the 0107 // previous step. 0108 #ifdef DEBUG_PFB 0109 qCDebug(OkularDviDebug) << "No encoding given: using charmap platform=7, encoding=2 that is contained in the font."; 0110 #endif 0111 for (int i = 0; i < 256; i++) { 0112 charMap[i] = FT_Get_Char_Index(face, i); 0113 } 0114 } else { 0115 if ((found == nullptr) && (face->charmap != nullptr)) { 0116 #ifdef DEBUG_PFB 0117 qCDebug(OkularDviDebug) << "No encoding given: using charmap platform=" << face->charmap->platform_id << ", encoding=" << face->charmap->encoding_id << " that is contained in the font."; 0118 #endif 0119 for (int i = 0; i < 256; i++) { 0120 charMap[i] = FT_Get_Char_Index(face, i); 0121 } 0122 } else { 0123 // As a last resort, we use the identity map. 0124 #ifdef DEBUG_PFB 0125 qCDebug(OkularDviDebug) << "No encoding given, no suitable charmaps found in the font: using identity charmap."; 0126 #endif 0127 for (int i = 0; i < 256; i++) { 0128 charMap[i] = i; 0129 } 0130 } 0131 } 0132 } 0133 } 0134 0135 TeXFont_PFB::~TeXFont_PFB() 0136 { 0137 FT_Done_Face(face); 0138 } 0139 0140 glyph *TeXFont_PFB::getGlyph(quint16 ch, bool generateCharacterPixmap, const QColor &color) 0141 { 0142 #ifdef DEBUG_PFB 0143 qCDebug(OkularDviDebug) << "TeXFont_PFB::getGlyph( ch=" << ch << ", '" << (char)(ch) << "', generateCharacterPixmap=" << generateCharacterPixmap << " )"; 0144 #endif 0145 0146 // Paranoia checks 0147 if (ch >= TeXFontDefinition::max_num_of_chars_in_font) { 0148 qCCritical(OkularDviDebug) << "TeXFont_PFB::getGlyph(): Argument is too big."; 0149 return glyphtable; 0150 } 0151 0152 // This is the address of the glyph that will be returned. 0153 glyph *g = glyphtable + ch; 0154 0155 if (fatalErrorInFontLoading == true) { 0156 return g; 0157 } 0158 0159 if ((generateCharacterPixmap == true) && ((g->shrunkenCharacter.isNull()) || (color != g->color))) { 0160 int error; 0161 unsigned int res = (unsigned int)(parent->displayResolution_in_dpi / parent->enlargement + 0.5); 0162 g->color = color; 0163 0164 // Character height in 1/64th of points (reminder: 1 pt = 1/72 inch) 0165 // Only approximate, may vary from file to file!!!! @@@@@ 0166 0167 long int characterSize_in_printers_points_by_64 = (long int)((64.0 * 72.0 * parent->scaled_size_in_DVI_units * parent->font_pool->getCMperDVIunit()) / 2.54 + 0.5); 0168 error = FT_Set_Char_Size(face, 0, characterSize_in_printers_points_by_64, res, res); 0169 if (error) { 0170 QString msg = i18n("FreeType reported an error when setting the character size for font file %1.", parent->filename); 0171 if (errorMessage.isEmpty()) { 0172 errorMessage = msg; 0173 } 0174 qCCritical(OkularDviDebug) << msg; 0175 g->shrunkenCharacter = QImage(1, 1, QImage::Format_RGB32); 0176 g->shrunkenCharacter.fill(qRgb(255, 255, 255)); 0177 return g; 0178 } 0179 0180 // load glyph image into the slot and erase the previous one 0181 if (parent->font_pool->getUseFontHints() == true) { 0182 error = FT_Load_Glyph(face, charMap[ch], FT_LOAD_DEFAULT); 0183 } else { 0184 error = FT_Load_Glyph(face, charMap[ch], FT_LOAD_NO_HINTING); 0185 } 0186 0187 if (error) { 0188 QString msg = i18n("FreeType is unable to load glyph #%1 from font file %2.", ch, parent->filename); 0189 if (errorMessage.isEmpty()) { 0190 errorMessage = msg; 0191 } 0192 qCCritical(OkularDviDebug) << msg; 0193 g->shrunkenCharacter = QImage(1, 1, QImage::Format_RGB32); 0194 g->shrunkenCharacter.fill(qRgb(255, 255, 255)); 0195 return g; 0196 } 0197 0198 // convert to an anti-aliased bitmap 0199 error = FT_Render_Glyph(face->glyph, ft_render_mode_normal); 0200 if (error) { 0201 QString msg = i18n("FreeType is unable to render glyph #%1 from font file %2.", ch, parent->filename); 0202 if (errorMessage.isEmpty()) { 0203 errorMessage = msg; 0204 } 0205 qCCritical(OkularDviDebug) << msg; 0206 g->shrunkenCharacter = QImage(1, 1, QImage::Format_RGB32); 0207 g->shrunkenCharacter.fill(qRgb(255, 255, 255)); 0208 return g; 0209 } 0210 0211 FT_GlyphSlot slot = face->glyph; 0212 0213 if ((slot->bitmap.width == 0) || (slot->bitmap.rows == 0)) { 0214 if (errorMessage.isEmpty()) { 0215 errorMessage = i18n("Glyph #%1 is empty.", ch); 0216 } 0217 qCCritical(OkularDviDebug) << i18n("Glyph #%1 from font file %2 is empty.", ch, parent->filename); 0218 g->shrunkenCharacter = QImage(15, 15, QImage::Format_RGB32); 0219 g->shrunkenCharacter.fill(qRgb(255, 0, 0)); 0220 g->x2 = 0; 0221 g->y2 = 15; 0222 } else { 0223 QImage imgi(slot->bitmap.width, slot->bitmap.rows, QImage::Format_ARGB32); 0224 0225 // Do QPixmaps fully support the alpha channel? If yes, we use 0226 // that. Otherwise, use other routines as a fallback 0227 if (parent->font_pool->QPixmapSupportsAlpha) { 0228 // If the alpha channel is properly supported, we set the 0229 // character glyph to a colored rectangle, and define the 0230 // character outline only using the alpha channel. That 0231 // ensures good quality rendering for overlapping characters. 0232 uchar *srcScanLine = slot->bitmap.buffer; 0233 for (unsigned int row = 0; row < slot->bitmap.rows; row++) { 0234 uchar *destScanLine = imgi.scanLine(row); 0235 for (unsigned int col = 0; col < slot->bitmap.width; col++) { 0236 destScanLine[4 * col + 0] = color.blue(); 0237 destScanLine[4 * col + 1] = color.green(); 0238 destScanLine[4 * col + 2] = color.red(); 0239 destScanLine[4 * col + 3] = srcScanLine[col]; 0240 } 0241 srcScanLine += slot->bitmap.pitch; 0242 } 0243 } else { 0244 // If the alpha channel is not supported... QT seems to turn 0245 // the alpha channel into a crude bitmap which is used to mask 0246 // the resulting QPixmap. In this case, we define the 0247 // character outline using the image data, and use the alpha 0248 // channel only to store "maximally opaque" or "completely 0249 // transparent" values. When characters are rendered, 0250 // overlapping characters are no longer correctly drawn, but 0251 // quality is still sufficient for most purposes. One notable 0252 // exception is output from the gftodvi program, which will be 0253 // partially unreadable. 0254 quint16 rInv = 0xFF - color.red(); 0255 quint16 gInv = 0xFF - color.green(); 0256 quint16 bInv = 0xFF - color.blue(); 0257 0258 for (unsigned int y = 0; y < slot->bitmap.rows; y++) { 0259 quint8 *srcScanLine = slot->bitmap.buffer + y * slot->bitmap.pitch; 0260 unsigned int *destScanLine = reinterpret_cast<unsigned int *>(imgi.scanLine(y)); 0261 for (unsigned int col = 0; col < slot->bitmap.width; col++) { 0262 quint16 data = *srcScanLine; 0263 // The value stored in "data" now has the following meaning: 0264 // data = 0 -> white; data = 0xff -> use "color" 0265 *destScanLine = qRgba(0xFF - (rInv * data + 0x7F) / 0xFF, 0xFF - (gInv * data + 0x7F) / 0xFF, 0xFF - (bInv * data + 0x7F) / 0xFF, (data > 0x03) ? 0xff : 0x00); 0266 destScanLine++; 0267 srcScanLine++; 0268 } 0269 } 0270 } 0271 0272 g->shrunkenCharacter = imgi; 0273 g->x2 = -slot->bitmap_left; 0274 g->y2 = slot->bitmap_top; 0275 } 0276 } 0277 0278 // Load glyph width, if that hasn't been done yet. 0279 if (g->dvi_advance_in_units_of_design_size_by_2e20 == 0) { 0280 int error = FT_Load_Glyph(face, charMap[ch], FT_LOAD_NO_SCALE); 0281 if (error) { 0282 QString msg = i18n("FreeType is unable to load metric for glyph #%1 from font file %2.", ch, parent->filename); 0283 if (errorMessage.isEmpty()) { 0284 errorMessage = msg; 0285 } 0286 qCCritical(OkularDviDebug) << msg; 0287 g->dvi_advance_in_units_of_design_size_by_2e20 = 1; 0288 } 0289 g->dvi_advance_in_units_of_design_size_by_2e20 = (qint32)(((qint64)(1 << 20) * (qint64)face->glyph->metrics.horiAdvance) / (qint64)face->units_per_EM); 0290 } 0291 0292 return g; 0293 } 0294 0295 #endif // HAVE_FREETYPE