// define this in your Makefile or .gyp to enforce AA requests // which GDI ignores at small sizes. This flag guarantees AA // for rotated text, regardless of GDI's notions. //#define SK_ENFORCE_ROTATED_TEXT_AA_ON_WINDOWS
staticbool bothZero(SkScalar a, SkScalar b) { return0 == a && 0 == b;
}
// returns false if there is any non-90-rotation or skew staticbool isAxisAligned(const SkScalerContextRec& rec) { return0 == rec.fPreSkewX &&
(bothZero(rec.fPost2x2[0][1], rec.fPost2x2[1][0]) ||
bothZero(rec.fPost2x2[0][0], rec.fPost2x2[1][1]));
}
staticbool needToRenderWithSkia(const SkScalerContextRec& rec) { #ifdef SK_ENFORCE_ROTATED_TEXT_AA_ON_WINDOWS // What we really want to catch is when GDI will ignore the AA request and give // us BW instead. Smallish rotated text is one heuristic, so this code is just // an approximation. We shouldn't need to do this for larger sizes, but at those // sizes, the quality difference gets less and less between our general // scanconverter and GDI's. if (SkMask::kA8_Format == rec.fMaskFormat && !isAxisAligned(rec)) { return true;
} #endif return rec.getHinting() == SkFontHinting::kNone || rec.getHinting() == SkFontHinting::kSlight;
}
if (!(textMetric.tmPitchAndFamily & TMPF_VECTOR)) { return textMetric.tmLastChar;
}
// The 'maxp' table stores the number of glyphs at offset 4, in 2 bytes.
uint16_t glyphs; if (GDI_ERROR != GetFontData(hdc, SkOTTableMaximumProfile::TAG, 4, &glyphs, sizeof(glyphs))) { return SkEndian_SwapBE16(glyphs);
}
// Binary search for glyph count. staticconst MAT2 mat2 = {{0, 1}, {0, 0}, {0, 0}, {0, 1}};
int32_t max = UINT16_MAX + 1;
int32_t min = 0;
GLYPHMETRICS gm; while (min < max) {
int32_t mid = min + ((max - min) / 2); if (GetGlyphOutlineW(hdc, mid, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0,
nullptr, &mat2) == GDI_ERROR) {
max = mid;
} else {
min = mid + 1;
}
}
SkASSERT(min == max); return min;
}
// The fixed pitch bit is set if the font is *not* fixed pitch.
this->setIsFixedPitch((textMetric.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0);
this->setFontStyle(SkFontStyle(textMetric.tmWeight, style.width(), style.slant()));
// Used a logfont on a memory context, should never get a device font. // Therefore all TMPF_DEVICE will be PostScript (cubic) fonts. // If the font has cubic outlines, it will not be rendered with ClearType.
fCanBeLCD = !((textMetric.tmPitchAndFamily & TMPF_VECTOR) &&
(textMetric.tmPitchAndFamily & TMPF_DEVICE));
}
/** *ThecreatedSkTypefacetakesownershipoffontMemResource.
*/
sk_sp<SkTypeface> SkCreateFontMemResourceTypefaceFromLOGFONT(const LOGFONT& origLF, HANDLE fontMemResource) {
LOGFONT lf = origLF;
make_canonical(&lf); // We'll never get a cache hit, so no point in putting this in SkTypefaceCache. return FontMemResourceTypeface::Make(lf, fontMemResource);
}
// Construct Glyph to Unicode table. // Unicode code points that require conjugate pairs in utf16 are not // supported. // TODO(arthurhsu): Add support for conjugate pairs. It looks like that may // require parsing the TTF cmap table (platform 4, encoding 12) directly instead // of calling GetFontUnicodeRange(). staticvoid populate_glyph_to_unicode(HDC fontHdc, constunsigned glyphCount,
SkSpan<SkUnichar> glyphToUnicode) {
sk_bzero(glyphToUnicode.data(), glyphToUnicode.size_bytes());
DWORD glyphSetBufferSize = GetFontUnicodeRanges(fontHdc, nullptr); if (!glyphSetBufferSize) { return;
}
for (DWORD i = 0; i < glyphSet->cRanges; ++i) { // There is no guarantee that within a Unicode range, the corresponding // glyph id in a font file are continuous. So, even if we have ranges, // we can't just use the first and last entry of the range to compute // result. We need to enumerate them one by one. int count = glyphSet->ranges[i].cGlyphs;
AutoTArray<WCHAR> chars(count + 1);
chars[count] = 0; // termintate string
AutoTArray<WORD> glyph(count); for (USHORT j = 0; j < count; ++j) {
chars[j] = glyphSet->ranges[i].wcLow + j;
}
GetGlyphIndicesW(fontHdc, chars.get(), count, glyph.get(),
GGI_MARK_NONEXISTING_GLYPHS); // If the glyph ID is valid, and the glyph is not mapped, then we will // fill in the char id into the vector. If the glyph is mapped already, // skip it. // TODO(arthurhsu): better improve this. e.g. Get all used char ids from // font cache, then generate this mapping table from there. It's // unlikely to have collisions since glyph reuse happens mostly for // different Unicode pages. for (USHORT j = 0; j < count; ++j) { if (glyph[j] != 0xFFFF && glyph[j] < glyphCount && glyphToUnicode[glyph[j]] == 0) {
glyphToUnicode[glyph[j]] = chars[j];
}
}
}
}
private:
HDC fDC{nullptr};
HFONT fSavefont{nullptr};
HBITMAP fBM{nullptr};
HFONT fFont{nullptr};
XFORM fXform{1, 0, 0, 1, 0, 0}; void* fBits{nullptr}; // points into fBM int fWidth{0}; int fHeight{0}; bool fIsBW{false};
};
void* HDCOffscreen::draw(const SkGlyph& glyph, bool isBW, size_t* srcRBPtr) { // Can we share the scalercontext's fDDC, so we don't need to create // a separate fDC here? if (nullptr == fDC) {
fDC = CreateCompatibleDC(0); if (nullptr == fDC) { return nullptr;
}
SetGraphicsMode(fDC, GM_ADVANCED);
SetBkMode(fDC, TRANSPARENT);
SetTextAlign(fDC, TA_LEFT | TA_BASELINE);
fSavefont = (HFONT)SelectObject(fDC, fFont);
HDCOffscreen fOffscreen; /** fGsA is the non-rotational part of total matrix without the text height scale. *Usedtofindthemagnitudeofadvances.
*/
MAT2 fGsA; /** The total matrix without the textSize. */
MAT2 fMat22; /** Scales font to EM size. */
MAT2 fHighResMat22;
HDC fDDC;
HFONT fSavefont;
HFONT fFont;
SCRIPT_CACHE fSC;
/** The total matrix which also removes EM scale. */
SkMatrix fHiResMatrix; /** fG_inv is the inverse of the rotational part of the total matrix. *Usedtosetthedirectionofadvances.
*/
SkMatrix fG_inv; enum Type {
kTrueType_Type, kBitmap_Type, kLine_Type
} fType; bool fGenerateFromPath;
TEXTMETRIC fTM;
};
// When GDI hinting, remove the entire Y scale from sA and GsA. (Prevents 'linear' metrics.) // When not hinting, remove only the integer Y scale from sA and GsA. (Applied by GDI.)
SkScalerContextRec::PreMatrixScale scaleConstraints =
(fRec.getHinting() == SkFontHinting::kNone || fRec.getHinting() == SkFontHinting::kSlight)
? SkScalerContextRec::PreMatrixScale::kVerticalInteger
: SkScalerContextRec::PreMatrixScale::kVertical;
SkVector scale;
SkMatrix sA;
SkMatrix GsA;
SkMatrix A;
fRec.computeMatrices(scaleConstraints, &scale, &sA, &GsA, &fG_inv, &A);
fGsA.eM11 = SkScalarToFIXED(GsA.get(SkMatrix::kMScaleX));
fGsA.eM12 = SkScalarToFIXED(-GsA.get(SkMatrix::kMSkewY)); // This should be ~0.
fGsA.eM21 = SkScalarToFIXED(-GsA.get(SkMatrix::kMSkewX));
fGsA.eM22 = SkScalarToFIXED(GsA.get(SkMatrix::kMScaleY));
// When not hinting, scale was computed with kVerticalInteger, so is already an integer. // The sA and GsA transforms will be used to create 'linear' metrics.
// When hinting, scale was computed with kVertical, stating that our port can handle // non-integer scales. This is done so that sA and GsA are computed without any 'residual' // scale in them, preventing 'linear' metrics. However, GDI cannot actually handle non-integer // scales so we need to round in this case. This is fine, since all of the scale has been // removed from sA and GsA, so GDI will be handling the scale completely.
SkScalar gdiTextSize = SkScalarRoundToScalar(scale.fY);
// GDI will not accept a size of zero, so round the range [0, 1] to 1. // If the size was non-zero, the scale factors will also be non-zero and 1px tall text is drawn. // If the size actually was zero, the scale factors will also be zero, so GDI will draw nothing. if (gdiTextSize == 0) {
gdiTextSize = SK_Scalar1;
}
if (0 == GetTextMetrics(fDDC, &fTM)) {
call_ensure_accessible(lf); if (0 == GetTextMetrics(fDDC, &fTM)) {
fTM.tmPitchAndFamily = TMPF_TRUETYPE;
}
}
XFORM xform; if (fTM.tmPitchAndFamily & TMPF_VECTOR) { // Used a logfont on a memory context, should never get a device font. // Therefore all TMPF_DEVICE will be PostScript fonts.
// If TMPF_VECTOR is set, one of TMPF_TRUETYPE or TMPF_DEVICE means that // we have an outline font. Otherwise we have a vector FON, which is // scalable, but not an outline font. // This was determined by testing with Type1 PFM/PFB and // OpenTypeCFF OTF, as well as looking at Wine bugs and sources. if (fTM.tmPitchAndFamily & (TMPF_TRUETYPE | TMPF_DEVICE)) { // Truetype or PostScript.
fType = SkScalerContext_GDI::kTrueType_Type;
} else { // Stroked FON.
fType = SkScalerContext_GDI::kLine_Type;
}
// fPost2x2 is column-major, left handed (y down). // XFORM 2x2 is row-major, left handed (y down).
xform.eM11 = SkScalarToFloat(sA.get(SkMatrix::kMScaleX));
xform.eM12 = SkScalarToFloat(sA.get(SkMatrix::kMSkewY));
xform.eM21 = SkScalarToFloat(sA.get(SkMatrix::kMSkewX));
xform.eM22 = SkScalarToFloat(sA.get(SkMatrix::kMScaleY));
xform.eDx = 0;
xform.eDy = 0;
// Bitmap FON cannot underhang, but vector FON may. // There appears no means of determining underhang of vector FON. int left = 0; int top = -fTM.tmAscent;
// These do not have an outline path at all.
mx.neverRequestPath = true; return mx;
}
UINT glyphId = glyph.getGlyphID();
GLYPHMETRICS gm;
sk_bzero(&gm, sizeof(gm));
DWORD status = GetGlyphOutlineW(fDDC, glyphId, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, nullptr, &fMat22); if (GDI_ERROR == status) {
LogFontTypeface::EnsureAccessible(this->getTypeface());
status = GetGlyphOutlineW(fDDC, glyphId, GGO_METRICS | GGO_GLYPH_INDEX, &gm, 0, nullptr, &fMat22); if (GDI_ERROR == status) { return mx;
}
}
bool empty = false; // The black box is either the embedded bitmap size or the outline extent. // It is 1x1 if nothing is to be drawn, but will also be 1x1 if something very small // is to be drawn, like a '.'. We need to outset '.' but do not wish to outset ' '. if (1 == gm.gmBlackBoxX && 1 == gm.gmBlackBoxY) { // If GetGlyphOutline with GGO_NATIVE returns 0, we know there was no outline.
DWORD bufferSize = GetGlyphOutlineW(fDDC, glyphId, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, 0, nullptr, &fMat22);
empty = (0 == bufferSize);
}
if (!empty) { int y = -gm.gmptGlyphOrigin.y; int x = gm.gmptGlyphOrigin.x; // Outset, since the image may bleed out of the black box. // For embedded bitmaps the black box should be exact. // For outlines we need to outset by 1 in all directions for bleed. // For ClearType we need to outset by 2 for bleed.
mx.bounds = SkRect::MakeXYWH(x, y, gm.gmBlackBoxX, gm.gmBlackBoxY).makeOutset(2, 2);
} // TODO(benjaminwagner): What is the type of gm.gmCellInc[XY]?
mx.advance.fX = (float)((int)gm.gmCellIncX);
mx.advance.fY = (float)((int)gm.gmCellIncY);
staticvoid build_power_table(uint8_t table[], float ee) { for (int i = 0; i < 256; i++) { float x = i / 255.f;
x = std::pow(x, ee); int xx = SkScalarRoundToInt(x * 255);
table[i] = SkToU8(xx);
}
}
if (!isBW) { const uint8_t* table; //The offscreen contains a GDI blit if isAA and kGenA8FromLCD_Flag is not set. //Otherwise the offscreen contains a ClearType blit. if (isAA && !(fRec.fFlags & SkScalerContext::kGenA8FromLCD_Flag)) {
table = getInverseGammaTableGDI();
} else {
table = getInverseGammaTableClearType();
} //Note that the following cannot really be integrated into the //pre-blend, since we may not be applying the pre-blend; when we aren't //applying the pre-blend it means that a filter wants linear anyway. //Other code may also be applying the pre-blend, so we'd need another //one with this and one without.
SkGdiRGB* addr = (SkGdiRGB*)bits; for (int y = 0; y < glyph.height(); ++y) { for (int x = 0; x < glyph.width(); ++x) { int r = (addr[x] >> 16) & 0xFF; int g = (addr[x] >> 8) & 0xFF; int b = (addr[x] >> 0) & 0xFF;
addr[x] = (table[r] << 16) | (table[g] << 8) | table[b];
}
addr = SkTAddOffset<SkGdiRGB>(addr, srcRB);
}
}
size_t dstRB = glyph.rowBytes(); if (isBW) { const uint8_t* src = (const uint8_t*)bits;
uint8_t* dst = (uint8_t*)((char*)imageBuffer + (glyph.height() - 1) * dstRB); for (int y = 0; y < glyph.height(); y++) {
memcpy(dst, src, dstRB);
src += srcRB;
dst -= dstRB;
} if constexpr (kSkShowTextBlitCoverage) { if (glyph.width() > 0 && glyph.height() > 0) { int bitCount = glyph.width() & 7;
uint8_t* first = (uint8_t*)imageBuffer;
uint8_t* last = first + glyph.height() * dstRB - 1;
*first |= 1 << 7;
*last |= bitCount == 0 ? 1 : 1 << (8 - bitCount);
}
}
} elseif (isAA) { // since the caller may require A8 for maskfilters, we can't check for BW // ... until we have the caller tell us that explicitly const SkGdiRGB* src = (const SkGdiRGB*)bits; if (fPreBlend.isApplicable()) {
RGBToA8<true>(src, srcRB, glyph, imageBuffer, fPreBlend.fG);
} else {
RGBToA8<false>(src, srcRB, glyph, imageBuffer, fPreBlend.fG);
}
} else { // LCD16 const SkGdiRGB* src = (const SkGdiRGB*)bits;
SkASSERT(SkMask::kLCD16_Format == glyph.maskFormat()); if (fPreBlend.isApplicable()) {
RGBToLcd16<true>(src, srcRB, glyph, imageBuffer,
fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
} else {
RGBToLcd16<false>(src, srcRB, glyph, imageBuffer,
fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
}
}
}
WORD currentCurveType() { return fPointIter.fCurveType;
}
private: /** Iterates over all of the polygon headers in a glyphbuf. */ class GDIPolygonHeaderIter {
public:
GDIPolygonHeaderIter(const uint8_t* glyphbuf, DWORD total_size)
: fCurPolygon(reinterpret_cast<const TTPOLYGONHEADER*>(glyphbuf))
, fEndPolygon(SkTAddOffset<const TTPOLYGONHEADER>(glyphbuf, total_size))
{ }
/** Iterates over all of the polygon curves in a polygon header. */ class GDIPolygonCurveIter {
public:
GDIPolygonCurveIter() : fCurCurve(nullptr), fEndCurve(nullptr) { }
/** Iterates over all of the polygon points in a polygon curve. */ class GDIPolygonCurvePointIter {
public:
GDIPolygonCurvePointIter() : fCurveType(0), fCurPoint(nullptr), fEndPoint(nullptr) { }
/** It is possible for the hinted and unhinted versions of the same path to have *adifferentnumberofpointsduetoGDI'shandlingofflippedpoints. *Ifthisisdetected,thiswillreturnfalse.
*/ bool process(const uint8_t* glyphbuf, DWORD total_size, GDIGlyphbufferPointIter hintedYs);
};
while (cur_poly < end_poly) { const TTPOLYCURVE* pc = (const TTPOLYCURVE*)cur_poly; const POINTFX* apfx = pc->apfx; const WORD cpfx = pc->cpfx;
if (pc->wType == TT_PRIM_LINE) { for (uint16_t i = 0; i < cpfx; i++) {
POINTFX pnt_b = apfx[i]; if (this->currentIsNot(pnt_b)) {
this->goingTo(pnt_b);
fBuilder->lineTo( SkFIXEDToScalar(pnt_b.x),
-SkFIXEDToScalar(pnt_b.y));
}
}
}
if (pc->wType == TT_PRIM_QSPLINE) { for (uint16_t u = 0; u < cpfx - 1; u++) { // Walk through points in spline
POINTFX pnt_b = apfx[u]; // B is always the current point
POINTFX pnt_c = apfx[u+1];
if (u < cpfx - 2) { // If not on last spline, compute C
pnt_c.x = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.x),
SkFIXEDToFixed(pnt_c.x)));
pnt_c.y = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.y),
SkFIXEDToFixed(pnt_c.y)));
}
while (cur_poly < end_poly) { const TTPOLYCURVE* pc = (const TTPOLYCURVE*)cur_poly; const POINTFX* apfx = pc->apfx; const WORD cpfx = pc->cpfx;
if (pc->wType == TT_PRIM_LINE) { for (uint16_t i = 0; i < cpfx; i++) {
move_next_expected_hinted_point(hintedYs, hintedPoint);
POINTFX pnt_b = {apfx[i].x, hintedPoint->y}; if (this->currentIsNot(pnt_b)) {
this->goingTo(pnt_b);
fBuilder->lineTo( SkFIXEDToScalar(pnt_b.x),
-SkFIXEDToScalar(pnt_b.y));
}
}
}
if (pc->wType == TT_PRIM_QSPLINE) {
POINTFX currentPoint = apfx[0];
move_next_expected_hinted_point(hintedYs, hintedPoint); // only take the hinted y if it wasn't flipped if (hintedYs.currentCurveType() == TT_PRIM_QSPLINE) {
currentPoint.y = hintedPoint->y;
} for (uint16_t u = 0; u < cpfx - 1; u++) { // Walk through points in spline
POINTFX pnt_b = currentPoint;//pc->apfx[u]; // B is always the current point
POINTFX pnt_c = apfx[u+1];
move_next_expected_hinted_point(hintedYs, hintedPoint); // only take the hinted y if it wasn't flipped if (hintedYs.currentCurveType() == TT_PRIM_QSPLINE) {
pnt_c.y = hintedPoint->y;
}
currentPoint.x = pnt_c.x;
currentPoint.y = pnt_c.y;
if (u < cpfx - 2) { // If not on last spline, compute C
pnt_c.x = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.x),
SkFIXEDToFixed(pnt_c.x)));
pnt_c.y = SkFixedToFIXED(SkFixedAve(SkFIXEDToFixed(pnt_b.y),
SkFIXEDToFixed(pnt_c.y)));
}
DWORD total_size = GetGlyphOutlineW(fDDC, glyph, flags, &gm, BUFFERSIZE, glyphbuf->get(), &fMat22); // Sometimes GetGlyphOutlineW returns a number larger than BUFFERSIZE even if BUFFERSIZE > 0. // It has been verified that this does not involve a buffer overrun. if (GDI_ERROR == total_size || total_size > BUFFERSIZE) { // GDI_ERROR because the BUFFERSIZE was too small, or because the data was not accessible. // When the data is not accessable GetGlyphOutlineW fails rather quickly, // so just try to get the size. If that fails then ensure the data is accessible.
total_size = GetGlyphOutlineW(fDDC, glyph, flags, &gm, 0, nullptr, &fMat22); if (GDI_ERROR == total_size) {
LogFontTypeface::EnsureAccessible(this->getTypeface());
total_size = GetGlyphOutlineW(fDDC, glyph, flags, &gm, 0, nullptr, &fMat22); if (GDI_ERROR == total_size) { // GetGlyphOutlineW is known to fail for some characters, such as spaces. // In these cases, just return that the glyph does not have a shape. return0;
}
}
glyphbuf->reset(total_size);
DWORD ret = GetGlyphOutlineW(fDDC, glyph, flags, &gm, total_size, glyphbuf->get(), &fMat22); if (GDI_ERROR == ret) {
LogFontTypeface::EnsureAccessible(this->getTypeface());
ret = GetGlyphOutlineW(fDDC, glyph, flags, &gm, total_size, glyphbuf->get(), &fMat22); if (GDI_ERROR == ret) {
SkASSERT(false); return0;
}
}
} return total_size;
}
// Out of all the fonts on a typical Windows box, // 25% of glyphs require more than 2KB. // 1% of glyphs require more than 4KB. // 0.01% of glyphs require more than 8KB. // 8KB is less than 1% of the normal 1MB stack on Windows. // Note that some web fonts glyphs require more than 20KB. //static const DWORD BUFFERSIZE = (1 << 13);
//GDI only uses hinted outlines when axis aligned.
UINT format = GGO_NATIVE | GGO_GLYPH_INDEX; if (fRec.getHinting() == SkFontHinting::kNone || fRec.getHinting() == SkFontHinting::kSlight){
format |= GGO_UNHINTED;
}
AutoSTMalloc<BUFFERSIZE, uint8_t> glyphbuf(BUFFERSIZE);
DWORD total_size = getGDIGlyphPath(glyphID, format, &glyphbuf); if (0 == total_size) { return {};
}
SkGDIGeometrySink sinkXBufYIter(&builder); if (!sinkXBufYIter.process(glyphbuf, total_size,
GDIGlyphbufferPointIter(hintedGlyphbuf, hinted_total_size)))
{ // Both path and sinkXBufYIter are in the state they were in at the time of failure.
builder.reset();
SkGDIGeometrySink sink(&builder);
sink.process(glyphbuf, total_size);
}
} return {{builder.detach(), false}};
}
void LogFontTypeface::onGetFamilyName(SkString* familyName) const { // Get the actual name of the typeface. The logfont may not know this.
SkAutoHDC hdc(fLogFont);
dcfontname_to_skstring(hdc, fLogFont, familyName);
}
// The design HFONT must be destroyed after the HDC
using HFONT_T = typename std::remove_pointer<HFONT>::type;
std::unique_ptr<HFONT_T, SkFunctionObject<DeleteObject>> designFont;
SkAutoHDC hdc(lf);
// To request design units, create a logical font whose height is specified // as unitsPerEm.
OUTLINETEXTMETRIC otm; unsignedint otmRet = GetOutlineTextMetrics(hdc, sizeof(otm), &otm); if (0 == otmRet) {
call_ensure_accessible(lf);
otmRet = GetOutlineTextMetrics(hdc, sizeof(otm), &otm);
} if (!otmRet || !GetTextFace(hdc, LF_FACESIZE, lf.lfFaceName)) { return info;
}
lf.lfHeight = -SkToS32(otm.otmEMSquare);
designFont.reset(CreateFontIndirect(&lf));
SelectObject(hdc, designFont.get()); if (!GetOutlineTextMetrics(hdc, sizeof(otm), &otm)) { return info;
}
glyphCount = calculateGlyphCount(hdc, fLogFont);
info.reset(new SkAdvancedTypefaceMetrics);
SkOTTableOS2_V4::Type fsType; if (sizeof(fsType) == this->getTableData(SkTEndian_SwapBE32(SkOTTableOS2::TAG),
offsetof(SkOTTableOS2_V4, fsType), sizeof(fsType),
&fsType)) {
SkOTUtils::SetAdvancedTypefaceFlags(fsType, info.get());
} else { // If bit 1 is set, the font may not be embedded in a document. // If bit 1 is clear, the font can be embedded. // If bit 2 is set, the embedding is read-only. if (otm.otmfsType & 0x1) {
info->fFlags |= SkAdvancedTypefaceMetrics::kNotEmbeddable_FontFlag;
}
}
// If this bit is clear the font is a fixed pitch font. if (!(otm.otmTextMetrics.tmPitchAndFamily & TMPF_FIXED_PITCH)) {
info->fStyle |= SkAdvancedTypefaceMetrics::kFixedPitch_Style;
} if (otm.otmTextMetrics.tmItalic) {
info->fStyle |= SkAdvancedTypefaceMetrics::kItalic_Style;
} if (otm.otmTextMetrics.tmPitchAndFamily & FF_ROMAN) {
info->fStyle |= SkAdvancedTypefaceMetrics::kSerif_Style;
} elseif (otm.otmTextMetrics.tmPitchAndFamily & FF_SCRIPT) {
info->fStyle |= SkAdvancedTypefaceMetrics::kScript_Style;
}
// The main italic angle of the font, in tenths of a degree counterclockwise // from vertical.
info->fItalicAngle = otm.otmItalicAngle / 10;
info->fAscent = SkToS16(otm.otmTextMetrics.tmAscent);
info->fDescent = SkToS16(-otm.otmTextMetrics.tmDescent); // TODO(ctguil): Use alternate cap height calculation. // MSDN says otmsCapEmHeight is not support but it is returning a value on // my Win7 box.
info->fCapHeight = otm.otmsCapEmHeight;
info->fBBox =
SkIRect::MakeLTRB(otm.otmrcFontBox.left, otm.otmrcFontBox.top,
otm.otmrcFontBox.right, otm.otmrcFontBox.bottom);
// Figure out a good guess for StemV - Min width of i, I, !, 1. // This probably isn't very good with an italic font.
min_width = SHRT_MAX;
info->fStemV = 0; for (size_t i = 0; i < std::size(stem_chars); i++) {
ABC abcWidths; if (GetCharABCWidths(hdc, stem_chars[i], stem_chars[i], &abcWidths)) {
int16_t width = abcWidths.abcB; if (width > 0 && width < min_width) {
min_width = width;
info->fStemV = min_width;
}
}
}
return info;
}
//Placeholder representation of a Base64 encoded GUID from create_unique_font_name. #define BASE64_GUID_ID "XXXXXXXXXXXXXXXXXXXXXXXX" //Length of GUID representation from create_id, including nullptr terminator. #define BASE64_GUID_ID_LEN std::size(BASE64_GUID_ID)
// Does not affect ownership of stream. static sk_sp<SkTypeface> create_from_stream(std::unique_ptr<SkStreamAsset> stream) { // Create a unique and unpredictable font name. // Avoids collisions and access from CSS. char familyName[BASE64_GUID_ID_LEN]; constint familyNameSize = std::size(familyName); if (FAILED(create_unique_font_name(familyName, familyNameSize))) { return nullptr;
}
// Change the name of the font.
sk_sp<SkData> rewrittenFontData(SkOTUtils::RenameFont(stream.get(), familyName, familyNameSize-1)); if (nullptr == rewrittenFontData.get()) { return nullptr;
}
// Register the font with GDI.
HANDLE fontReference = activate_font(rewrittenFontData.get()); if (nullptr == fontReference) { return nullptr;
}
// Create the typeface.
LOGFONT lf;
logfont_for_name(familyName, &lf);
staticvoid bmpCharsToGlyphs(HDC hdc, const WCHAR* bmpChars, int count, uint16_t* glyphs, bool Ox1FHack)
{ // Type1 fonts fail with uniscribe API. Use GetGlyphIndices for plane 0.
/** Real documentation for GetGlyphIndicesW: * *WhenGGI_MARK_NONEXISTING_GLYPHSisnotspecifiedandacharacterdoesnotmaptoa *glyph,thenthe'defaultcharacter'sglyphisreturnedinstead.The'defaultcharacter' *isavailableinfTM.tmDefaultChar.FONfontshaveadefaultcharacter,andthereexists *ausDefaultCharinthe'OS/2'table,version2andlater.Ifthereisno *'defaultcharacter'specifiedbythefont,thenoftenthefirstcharacterfoundisused. * *WhenGGI_MARK_NONEXISTING_GLYPHSisspecifiedandacharacterdoesnotmaptoaglyph, *thentheglyph0xFFFFisused.InWindowsXPandearlier,Bitmap/VectorFONusuallyuse *glyph0x1Finstead('Terminal'appearstobespecial,returning0xFFFF). *Type1PFM/PFB,TT,OTTT,OTCFFallappeartouse0xFFFF,evenonXP.
*/
DWORD result = GetGlyphIndicesW(hdc, bmpChars, count, glyphs, GGI_MARK_NONEXISTING_GLYPHS); if (GDI_ERROR == result) { for (int i = 0; i < count; ++i) {
glyphs[i] = 0;
} return;
}
if (Ox1FHack) { for (int i = 0; i < count; ++i) { if (0xFFFF == glyphs[i] || 0x1F == glyphs[i]) {
glyphs[i] = 0;
}
}
} else { for (int i = 0; i < count; ++i) { if (0xFFFF == glyphs[i]){
glyphs[i] = 0;
}
}
}
}
static uint16_t nonBmpCharToGlyph(HDC hdc, SCRIPT_CACHE* scriptCache, const WCHAR utf16[2]) {
uint16_t index = 0; // Use uniscribe to detemine glyph index for non-BMP characters. staticconstint numWCHAR = 2; staticconstint maxItems = 2; // MSDN states that this can be nullptr, but some things don't work then.
SCRIPT_CONTROL scriptControl;
memset(&scriptControl, 0, sizeof(scriptControl)); // Add extra item to SCRIPT_ITEM to work around a bug (now documented). // https://bugzilla.mozilla.org/show_bug.cgi?id=366643
SCRIPT_ITEM si[maxItems + 1]; int numItems;
HRZM(ScriptItemize(utf16, numWCHAR, maxItems, &scriptControl, nullptr, si, &numItems), "Could not itemize character.");
// Sometimes ScriptShape cannot find a glyph for a non-BMP and returns 2 space glyphs. staticconstint maxGlyphs = 2;
SCRIPT_VISATTR vsa[maxGlyphs];
WORD outGlyphs[maxGlyphs];
WORD logClust[numWCHAR]; int numGlyphs;
SCRIPT_ANALYSIS& script = si[0].a;
script.eScript = SCRIPT_UNDEFINED;
script.fRTL = FALSE;
script.fLayoutRTL = FALSE;
script.fLinkBefore = FALSE;
script.fLinkAfter = FALSE;
script.fLogicalOrder = FALSE;
script.fNoGlyphIndex = FALSE;
script.s.uBidiLevel = 0;
script.s.fOverrideDirection = 0;
script.s.fInhibitSymSwap = TRUE;
script.s.fCharShape = FALSE;
script.s.fDigitSubstitute = FALSE;
script.s.fInhibitLigate = FALSE;
script.s.fDisplayZWG = TRUE;
script.s.fArabicNumContext = FALSE;
script.s.fGcpClusters = FALSE;
script.s.fReserved = 0;
script.s.fEngineReserved = 0; // For the future, 0x80040200 from here is USP_E_SCRIPT_NOT_IN_FONT
HRZM(ScriptShape(hdc, scriptCache, utf16, numWCHAR, maxGlyphs, &script,
outGlyphs, logClust, vsa, &numGlyphs), "Could not shape character."); if (1 == numGlyphs) {
index = outGlyphs[0];
} return index;
}
SkFontHinting h = rec->getHinting(); switch (h) { case SkFontHinting::kNone: break; case SkFontHinting::kSlight: // Only do slight hinting when axis aligned. // TODO: re-enable slight hinting when FontHostTest can pass. //if (!isAxisAligned(*rec)) {
h = SkFontHinting::kNone; //} break; case SkFontHinting::kNormal: case SkFontHinting::kFull: // TODO: need to be able to distinguish subpixel positioned glyphs // and linear metrics. //rec->fFlags &= ~SkScalerContext::kSubpixelPositioning_Flag;
h = SkFontHinting::kNormal; break; default:
SkDEBUGFAIL("unknown hinting");
} //TODO: if this is a bitmap font, squash hinting and subpixel.
rec->setHinting(h);
// turn this off since GDI might turn A8 into BW! Need a bigger fix. #if0 // Disable LCD when rotated, since GDI's output is ugly if (isLCD(*rec) && !isAxisAligned(*rec)) {
rec->fMaskFormat = SkMask::kA8_Format;
} #endif
if (!fCanBeLCD && isLCD(*rec)) {
rec->fMaskFormat = SkMask::kA8_Format;
rec->fFlags &= ~SkScalerContext::kGenA8FromLCD_Flag;
} elseif (rec->fMaskFormat == SkMask::kA8_Format) { // Bug 1277404 // If we have non LCD GDI text, render the fonts as cleartype and convert them // to grayscale. This seems to be what Chrome and IE are doing on Windows 7. // This also applies if cleartype is disabled system wide.
rec->fFlags |= SkScalerContext::kGenA8FromLCD_Flag;
}
}
staticbool valid_logfont_for_enum(const LOGFONT& lf) { // TODO: Vector FON is unsupported and should not be listed. return // Ignore implicit vertical variants.
lf.lfFaceName[0] && lf.lfFaceName[0] != '@'
// DEFAULT_CHARSET is used to get all fonts, but also implies all // character sets. Filter assuming all fonts support ANSI_CHARSET.
&& ANSI_CHARSET == lf.lfCharSet
;
}
/** An EnumFontFamExProc implementation which interprets builderParam as *anSkTDArray<ENUMLOGFONTEX>*andappendslogfontswhich *passthevalid_logfont_for_enumpredicate.
*/ staticint CALLBACK enum_family_proc(const LOGFONT* lf, const TEXTMETRIC*,
DWORD fontType, LPARAM builderParam) { if (valid_logfont_for_enum(*lf)) {
SkTDArray<ENUMLOGFONTEX>* array = (SkTDArray<ENUMLOGFONTEX>*)builderParam;
*array->append() = *(const ENUMLOGFONTEX*)lf;
} return1; // non-zero means continue
}
class SkFontStyleSetGDI : public SkFontStyleSet {
public:
SkFontStyleSetGDI(const TCHAR familyName[]) {
LOGFONT lf;
sk_bzero(&lf, sizeof(lf));
lf.lfCharSet = DEFAULT_CHARSET;
_tcscpy_s(lf.lfFaceName, familyName);
void getStyle(int index, SkFontStyle* fs, SkString* styleName) override { if (fs) {
*fs = get_style(fArray[index].elfLogFont);
} if (styleName) { const ENUMLOGFONTEX& ref = fArray[index]; // For some reason, ENUMLOGFONTEX and LOGFONT disagree on their type in the // non-unicode version. // ENUMLOGFONTEX uses BYTE // LOGFONT uses CHAR // Here we assert they that the style name is logically the same (size) as // a TCHAR, so we can use the same converter function.
SkASSERT(sizeof(TCHAR) == sizeof(ref.elfStyle[0]));
tchar_to_skstring((const TCHAR*)ref.elfStyle, styleName);
}
}
sk_sp<SkFontStyleSet> onMatchFamily(constchar familyName[]) const override { if (nullptr == familyName) {
familyName = ""; // do we need this check???
}
LOGFONT lf;
logfont_for_name(familyName, &lf); return sk_sp<SkFontStyleSet>(new SkFontStyleSetGDI(lf.lfFaceName));
}
sk_sp<SkTypeface> onMatchFamilyStyle(constchar familyName[], const SkFontStyle& fontstyle) const override { // could be in base impl
sk_sp<SkFontStyleSet> sset(this->matchFamily(familyName)); return sset->matchStyle(fontstyle);
}
sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData> data, int ttcIndex) const override { // could be in base impl return this->makeFromStream(std::unique_ptr<SkStreamAsset>(new SkMemoryStream(std::move(data))),
ttcIndex);
}
sk_sp<SkTypeface> onMakeFromFile(constchar path[], int ttcIndex) const override { // could be in base impl auto stream = SkStream::MakeFromFile(path); return stream ? this->makeFromStream(std::move(stream), ttcIndex) : nullptr;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.