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