pdfium/core/fxge/cfx_face.h
Tom Sepez d77801fc1b Fix font metrics and loading discrepancies in Skrifa wrapper
This CL resolves several discrepancies between FreeType and Skrifa
discovered when enabling Skia typeface checks in PDFium.

1. Roboto GID 167 bounds shift: TrueType static outlines in glyf tables
   are shifted by LSB - xMin. Skrifa's GlyphMetrics::bounds did not
   reflect this shift. Patched GlyphMetrics::bounds in read-fonts to
   apply the LSB shift for static TrueType fonts.
2. MinionPro-Regular GID 55 loading: CID-keyed CFF fonts present CID
   as GID in FreeType, but CharString index as GID in Skrifa.
   Implemented GID-to-CID mapping using the CFF charset table in the
   Rust wrapper. Exposed top-DICT CidCount as num_glyphs.
3. TimesNewRoman GID 104 and Roboto GID 2344 composite glyph bounds:
   TrueType composite glyphs with USE_MY_METRICS override the composite
   glyph's LSB with the component's LSB. Patched GlyphMetrics::bounds
   in read-fonts to detect this flag and compute the correct visual
   bounds shift using the base component's metrics.
4. Normalize family name comparisons: CFF subsetted fonts often have
   6-letter random prefixes (e.g., USBMJS+). Normalized family names
   by stripping these prefixes before comparing FreeType and Skrifa.
5. Added unit tests for CFF CID fonts and composite glyph bounds.
6. Use FX_RECT::Near() to account for rounding issues.
7. Add test fonts by extracting them from our test files
   latin_extended.pdf, bug_402562387.pdf, and  bug_781804.pdf. Since
   the underlying PDF files containing those exact font bytes were
   already in PDFium's test corpus, extracting their subsetted font
   streams as standalone test resources introduced no new external
   dependencies or licensing changes.

TAG=agy
CONV=43afe434-9e79-46ca-92ef-e9dcc93f7b38

Change-Id: Id4ecf70df83d1e950ff080f06cb7f90f2ed7fac1
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/150891
SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Tom Sepez <tsepez@chromium.org>
2026-07-20 15:08:57 -07:00

211 lines
6.7 KiB
C++

// Copyright 2019 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CORE_FXGE_CFX_FACE_H_
#define CORE_FXGE_CFX_FACE_H_
#include <stdint.h>
#include <array>
#include <memory>
#include <optional>
#include <vector>
#include "build/build_config.h"
#include "core/fxcrt/bytestring.h"
#include "core/fxcrt/cfx_read_only_span_stream.h"
#include "core/fxcrt/fx_coordinates.h"
#include "core/fxcrt/observed_ptr.h"
#include "core/fxcrt/retain_ptr.h"
#include "core/fxcrt/span.h"
#include "core/fxge/freetype/fx_freetype.h"
#include "core/fxge/fx_font.h"
#if defined(PDF_USE_SKIA)
#include "third_party/skia/include/core/SkRefCnt.h" // nogncheck
#endif
#if defined(PDF_ENABLE_FONTATIONS)
#include "third_party/rust/cxx/v1/cxx.h"
#endif // defined(PDF_ENABLE_FONTATIONS)
class CFX_CTTGSUBTable;
class CFX_GlyphBitmap;
class CFX_Path;
class CFX_SubstFont;
#if defined(PDF_ENABLE_XFA)
class CFX_CTTNameTable;
#endif // defined(PDF_ENABLE_XFA)
#if defined(PDF_USE_SKIA)
class SkTypeface;
#endif
#if defined(PDF_ENABLE_FONTATIONS)
struct SkrifaFontHolder;
#else
struct SkrifaFontHolder {};
#endif
namespace fxge {
enum class FontEncoding : uint32_t;
} // namespace fxge
class CFX_Face final : public Retainable, public Observable {
public:
using CharMap = void*;
// Note that this corresponds to the cmap header in fonts, and not the cmap
// data in PDFs.
struct CharMapId {
friend constexpr bool operator==(const CharMapId&,
const CharMapId&) = default;
int platform_id;
int encoding_id;
};
// Aliases for some commonly used cmaps.
static constexpr CharMapId kMacRomanCmapId{1, 0};
static constexpr CharMapId kWindowsSymbolCmapId{3, 0};
static constexpr CharMapId kWindowsUnicodeCmapId{3, 1};
static RetainPtr<CFX_Face> New(RetainPtr<Retainable> cache_entry,
RetainPtr<CFX_ReadOnlySpanStream> font_stream,
uint32_t face_index);
bool HasGlyphNames() const;
bool IsTtOt() const;
ByteString GetFontFormat();
bool IsFixedWidth() const;
bool IsItalic() const;
bool IsBold() const;
ByteString GetFamilyName() const;
ByteString GetStyleName() const;
FX_RECT GetBBox() const;
uint16_t GetUnitsPerEm() const;
int EmAdjust(int value) const;
int16_t GetAscender() const;
int16_t GetDescender() const;
pdfium::span<const uint8_t> GetData() const;
std::unique_ptr<CFX_CTTGSUBTable> ParseGSUBTable();
int GetGlyphCount() const;
FX_RECT GetGlyphBBox(uint32_t glyph_index) const;
std::optional<FX_RECT> GetFontGlyphBBox(uint32_t glyph_index);
std::unique_ptr<CFX_GlyphBitmap> RenderGlyph(uint32_t glyph_index,
bool is_cid_font,
bool is_vertical,
const CFX_Matrix& matrix,
int dest_width,
FontAntiAliasingMode anti_alias,
const CFX_SubstFont* subst_font);
std::unique_ptr<CFX_Path> LoadGlyphPath(uint32_t glyph_index,
int dest_width,
bool is_vertical,
const CFX_SubstFont* subst_font);
int GetGlyphTTWidth(uint32_t glyph_index) const;
int GetGlyphWidth(uint32_t glyph_index,
int dest_width,
int weight,
const CFX_SubstFont* subst_font);
ByteString GetGlyphName(uint32_t glyph_index);
int GetCharIndex(uint32_t code);
int GetNameIndex(const char* name);
FX_RECT GetCharBBox(uint32_t code, int glyph_index);
std::vector<CharCodeAndIndex> GetCharCodesAndIndices(char32_t max_char);
FT_Face GetFTFaceForTesting() { return GetRec(); }
CharMap GetCurrentCharMap() const;
std::optional<fxge::FontEncoding> GetCurrentCharMapEncoding() const;
CharMapId GetCharMapIdByIndex(size_t index) const;
int GetCharMapPlatformIdByIndex(size_t index) const;
fxge::FontEncoding GetCharMapEncodingByIndex(size_t index) const;
size_t GetCharMapCount() const;
int LoadGlyph(uint32_t glyph_index, bool scale);
ByteString GetPostscriptName();
void SetCharMap(CharMap map);
void SetCharMapByIndex(size_t index);
bool SelectCharMap(fxge::FontEncoding encoding);
#if defined(PDF_ENABLE_XFA) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)
// Returns enum FontStyle values.
uint32_t GetFontStyle();
std::optional<std::array<uint32_t, 2>> GetOs2CodePageRange();
#endif
#if defined(PDF_ENABLE_XFA)
bool IsScalable() const;
int GetNumFaces() const;
std::unique_ptr<CFX_CTTNameTable> ParseNameTable();
std::optional<std::array<uint32_t, 4>> GetOs2UnicodeRange();
#endif
#if BUILDFLAG(IS_WIN)
bool CanEmbed();
#endif
#if defined(PDF_USE_SKIA)
SkTypeface* GetOrCreateSkTypeface();
#endif
private:
CFX_Face(RetainPtr<Retainable> cache_entry,
RetainPtr<CFX_ReadOnlySpanStream> font_stream,
FT_FaceRec* rec,
std::unique_ptr<SkrifaFontHolder> skrifa_font);
~CFX_Face() override;
FT_FaceRec* GetRec() { return rec_.get(); }
const FT_FaceRec* GetRec() const { return rec_.get(); }
int GetCharMapEncodingIdByIndex(size_t index) const;
CFX_Size GetPixelSize() const;
bool IsTricky() const;
void AdjustVariationParams(int glyph_index, int dest_width, int weight);
pdfium::span<const FT_CharMap> GetCharMaps() const;
// Returns the size of the data, or 0 on failure. Only write into `buffer` if
// it is large enough to hold the data.
size_t GetSfntTable(uint32_t table, pdfium::span<uint8_t> buffer);
#if defined(PDF_ENABLE_XFA) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)
std::optional<std::array<uint8_t, 2>> GetOs2Panose();
#endif
// `cache_entry_` must outlive `font_stream_`. Faces managed by a cache
// and sharing the same `font_stream_` keep the cache entry that indexes
// that stream alive via this member while there is at least one face
// using it. This may be nullptr for faces not managed by a cache.
RetainPtr<Retainable> cache_entry_;
// `font_stream_` must outlive `rec_` and `skia_typeface_`. Faces keep
// the actual data backing the `rec_` and `skia_typeface_` alive via
// this member while the `rec_` and `skia_typeface_` is still using it.
RetainPtr<CFX_ReadOnlySpanStream> font_stream_;
ScopedFXFTFaceRec const rec_;
#if defined(PDF_USE_SKIA)
sk_sp<SkTypeface> skia_typeface_;
#endif // defined(PDF_USE_SKIA)
#if defined(PDF_ENABLE_FONTATIONS)
std::unique_ptr<SkrifaFontHolder> const skrifa_font_;
#endif // defined(PDF_ENABLE_FONTATIONS)
};
#endif // CORE_FXGE_CFX_FACE_H_