/* ** Copyright 2006, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ #include #include "SkFontHost.h" #include "SkDescriptor.h" #include "SkEndian.h" #include "SkPaint.h" #include "SkPoint.h" // Give 1MB font cache budget #define FONT_CACHE_MEMORY_BUDGET (1024 * 1024) const char* gDefaultfont = "Arial"; // hard code for now static SkMutex gFTMutex; static inline SkPoint F32PtToSkPoint(const Float32Point p) { SkPoint sp = { SkFloatToScalar(p.x), SkFloatToScalar(p.y) }; return sp; } static inline uint32_t _rotl(uint32_t v, uint32_t r) { return (v << r | v >> (32 - r)); } class SkTypeface_Mac : public SkTypeface { public: SkTypeface_Mac(SkTypeface::Style style, uint32_t id) : SkTypeface(style, id) {} }; #pragma mark - static uint32_t find_from_name(const char name[]) { CFStringRef str = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8); uint32_t fontID = ::ATSFontFindFromName(str, kATSOptionFlagsDefault); CFRelease(str); return fontID; } static uint32_t find_default_fontID() { static const char* gDefaultNames[] = { "Arial", "Tahoma", "Helvetica" }; uint32_t fontID; for (size_t i = 0; i < SK_ARRAY_COUNT(gDefaultNames); i++) { fontID = find_from_name(gDefaultNames[i]); if (fontID) { return fontID; } } sk_throw(); return 0; } static SkTypeface* CreateTypeface_(const char name[], SkTypeface::Style style) { uint32_t fontID = 0; if (NULL != name) { fontID = find_from_name(name); } if (0 == fontID) { fontID = find_default_fontID(); } // we lie (for now) and report that we found the exact style bits return new SkTypeface_Mac(style, fontID); } #pragma mark - class SkScalerContext_Mac : public SkScalerContext { public: SkScalerContext_Mac(const SkDescriptor* desc); virtual ~SkScalerContext_Mac(); protected: virtual unsigned generateGlyphCount() const; virtual uint16_t generateCharToGlyph(SkUnichar uni); virtual void generateAdvance(SkGlyph* glyph); virtual void generateMetrics(SkGlyph* glyph); virtual void generateImage(const SkGlyph& glyph); virtual void generatePath(const SkGlyph& glyph, SkPath* path); virtual void generateFontMetrics(SkPaint::FontMetrics* mX, SkPaint::FontMetrics* mY); private: ATSUTextLayout fLayout; ATSUStyle fStyle; CGColorSpaceRef fGrayColorSpace; CGAffineTransform fTransform; static OSStatus MoveTo(const Float32Point *pt, void *cb); static OSStatus Line(const Float32Point *pt, void *cb); static OSStatus Curve(const Float32Point *pt1, const Float32Point *pt2, const Float32Point *pt3, void *cb); static OSStatus Close(void *cb); }; void SkFontHost::FilterRec(SkScalerContext::Rec* rec) { // we only support 2 levels of hinting SkPaint::Hinting h = rec->getHinting(); if (SkPaint::kSlight_Hinting == h) { h = SkPaint::kNo_Hinting; } else if (SkPaint::kFull_Hinting == h) { h = SkPaint::kNormal_Hinting; } rec->setHinting(h); // we don't support LCD text if (SkMask::FormatIsLCD((SkMask::Format)rec->fMaskFormat)) { rec->fMaskFormat = SkMask::kA8_Format; } } SkScalerContext_Mac::SkScalerContext_Mac(const SkDescriptor* desc) : SkScalerContext(desc), fLayout(0), fStyle(0) { SkAutoMutexAcquire ac(gFTMutex); OSStatus err; err = ::ATSUCreateStyle(&fStyle); SkASSERT(0 == err); SkMatrix m; fRec.getSingleMatrix(&m); fTransform = CGAffineTransformMake(SkScalarToFloat(m[SkMatrix::kMScaleX]), SkScalarToFloat(m[SkMatrix::kMSkewX]), SkScalarToFloat(m[SkMatrix::kMSkewY]), SkScalarToFloat(m[SkMatrix::kMScaleY]), SkScalarToFloat(m[SkMatrix::kMTransX]), SkScalarToFloat(m[SkMatrix::kMTransY])); ATSStyleRenderingOptions renderOpts = kATSStyleApplyAntiAliasing; switch (fRec.getHinting()) { case SkPaint::kNo_Hinting: case SkPaint::kSlight_Hinting: renderOpts |= kATSStyleNoHinting; break; case SkPaint::kNormal_Hinting: case SkPaint::kFull_Hinting: renderOpts |= kATSStyleApplyHints; break; } ATSUFontID fontID = FMGetFontFromATSFontRef(fRec.fFontID); // we put everything in the matrix, so our pt size is just 1.0 Fixed fixedSize = SK_Fixed1; static const ATSUAttributeTag tags[] = { kATSUFontTag, kATSUSizeTag, kATSUFontMatrixTag, kATSUStyleRenderingOptionsTag }; static const ByteCount sizes[] = { sizeof(fontID), sizeof(fixedSize), sizeof(fTransform), sizeof(renderOpts) }; const ATSUAttributeValuePtr values[] = { &fontID, &fixedSize, &fTransform, &renderOpts }; err = ::ATSUSetAttributes(fStyle, SK_ARRAY_COUNT(tags), tags, sizes, values); SkASSERT(0 == err); err = ::ATSUCreateTextLayout(&fLayout); SkASSERT(0 == err); fGrayColorSpace = ::CGColorSpaceCreateDeviceGray(); } SkScalerContext_Mac::~SkScalerContext_Mac() { ::CGColorSpaceRelease(fGrayColorSpace); ::ATSUDisposeTextLayout(fLayout); ::ATSUDisposeStyle(fStyle); } // man, we need to consider caching this, since it is just dependent on // fFontID, and not on any of the other settings like matrix or flags unsigned SkScalerContext_Mac::generateGlyphCount() const { // The 'maxp' table stores the number of glyphs a offset 4, in 2 bytes uint16_t numGlyphs; if (SkFontHost::GetTableData(fRec.fFontID, SkSetFourByteTag('m', 'a', 'x', 'p'), 4, 2, &numGlyphs) != 2) { return 0xFFFF; } return SkEndian_SwapBE16(numGlyphs); } uint16_t SkScalerContext_Mac::generateCharToGlyph(SkUnichar uni) { SkAutoMutexAcquire ac(gFTMutex); OSStatus err; UniChar achar = uni; err = ::ATSUSetTextPointerLocation(fLayout,&achar,0,1,1); err = ::ATSUSetRunStyle(fLayout,fStyle,kATSUFromTextBeginning,kATSUToTextEnd); ATSLayoutRecord *layoutPtr; ItemCount count; ATSGlyphRef glyph; err = ::ATSUDirectGetLayoutDataArrayPtrFromTextLayout(fLayout,0,kATSUDirectDataLayoutRecordATSLayoutRecordCurrent,(void**)&layoutPtr,&count); glyph = layoutPtr->glyphID; ::ATSUDirectReleaseLayoutDataArrayPtr(NULL,kATSUDirectDataLayoutRecordATSLayoutRecordCurrent,(void**)&layoutPtr); return glyph; } static void set_glyph_metrics_on_error(SkGlyph* glyph) { glyph->fRsbDelta = 0; glyph->fLsbDelta = 0; glyph->fWidth = 0; glyph->fHeight = 0; glyph->fTop = 0; glyph->fLeft = 0; glyph->fAdvanceX = 0; glyph->fAdvanceY = 0; } void SkScalerContext_Mac::generateAdvance(SkGlyph* glyph) { this->generateMetrics(glyph); } void SkScalerContext_Mac::generateMetrics(SkGlyph* glyph) { GlyphID glyphID = glyph->getGlyphID(fBaseGlyphCount); ATSGlyphScreenMetrics screenMetrics; ATSGlyphIdealMetrics idealMetrics; OSStatus err = ATSUGlyphGetScreenMetrics(fStyle, 1, &glyphID, 0, true, true, &screenMetrics); if (noErr != err) { set_glyph_metrics_on_error(glyph); return; } err = ATSUGlyphGetIdealMetrics(fStyle, 1, &glyphID, 0, &idealMetrics); if (noErr != err) { set_glyph_metrics_on_error(glyph); return; } if (!fRec.fSubpixelPositioning) { glyph->fAdvanceX = SkFloatToFixed(screenMetrics.deviceAdvance.x); glyph->fAdvanceY = -SkFloatToFixed(screenMetrics.deviceAdvance.y); } else { glyph->fAdvanceX = SkFloatToFixed(idealMetrics.advance.x); glyph->fAdvanceY = -SkFloatToFixed(idealMetrics.advance.y); } // specify an extra 1-pixel border, go tive CG room for its antialiasing // i.e. without this, I was seeing some edges chopped off! glyph->fWidth = screenMetrics.width + 2; glyph->fHeight = screenMetrics.height + 2; glyph->fLeft = sk_float_round2int(screenMetrics.topLeft.x) - 1; glyph->fTop = -sk_float_round2int(screenMetrics.topLeft.y) - 1; } void SkScalerContext_Mac::generateImage(const SkGlyph& glyph) { SkAutoMutexAcquire ac(gFTMutex); SkASSERT(fLayout); sk_bzero(glyph.fImage, glyph.fHeight * glyph.rowBytes()); CGContextRef contextRef = ::CGBitmapContextCreate(glyph.fImage, glyph.fWidth, glyph.fHeight, 8, glyph.rowBytes(), fGrayColorSpace, kCGImageAlphaNone); if (!contextRef) { SkASSERT(false); return; } ::CGContextSetGrayFillColor(contextRef, 1.0, 1.0); ::CGContextSetTextDrawingMode(contextRef, kCGTextFill); CGGlyph glyphID = glyph.getGlyphID(fBaseGlyphCount); CGFontRef fontRef = CGFontCreateWithPlatformFont(&fRec.fFontID); CGContextSetFont(contextRef, fontRef); CGContextSetFontSize(contextRef, 1); CGContextSetTextMatrix(contextRef, fTransform); CGContextShowGlyphsAtPoint(contextRef, -glyph.fLeft, glyph.fTop + glyph.fHeight, &glyphID, 1); ::CGContextRelease(contextRef); } #if 0 static void convert_metrics(SkPaint::FontMetrics* dst, const ATSFontMetrics& src) { dst->fTop = -SkFloatToScalar(src.ascent); dst->fAscent = -SkFloatToScalar(src.ascent); dst->fDescent = SkFloatToScalar(src.descent); dst->fBottom = SkFloatToScalar(src.descent); dst->fLeading = SkFloatToScalar(src.leading); } #endif static void* get_font_table(ATSFontRef fontID, uint32_t tag) { ByteCount size; OSStatus err = ATSFontGetTable(fontID, tag, 0, 0, NULL, &size); if (err) { return NULL; } void* data = sk_malloc_throw(size); err = ATSFontGetTable(fontID, tag, 0, size, data, &size); if (err) { sk_free(data); data = NULL; } return data; } static int get_be16(const void* data, size_t offset) { const char* ptr = reinterpret_cast(data); uint16_t value = *reinterpret_cast(ptr + offset); int n = SkEndian_SwapBE16(value); // now force it to be signed return n << 16 >> 16; } #define SFNT_HEAD_UPEM_OFFSET 18 #define SFNT_HEAD_YMIN_OFFSET 38 #define SFNT_HEAD_YMAX_OFFSET 42 #define SFNT_HEAD_STYLE_OFFSET 44 #define SFNT_HHEA_ASCENT_OFFSET 4 #define SFNT_HHEA_DESCENT_OFFSET 6 #define SFNT_HHEA_LEADING_OFFSET 8 static bool init_vertical_metrics(ATSFontRef font, SkPoint pts[5]) { void* head = get_font_table(font, 'head'); if (NULL == head) { return false; } void* hhea = get_font_table(font, 'hhea'); if (NULL == hhea) { sk_free(head); return false; } int upem = get_be16(head, SFNT_HEAD_UPEM_OFFSET); int ys[5]; ys[0] = -get_be16(head, SFNT_HEAD_YMAX_OFFSET); ys[1] = -get_be16(hhea, SFNT_HHEA_ASCENT_OFFSET); ys[2] = -get_be16(hhea, SFNT_HHEA_DESCENT_OFFSET); ys[3] = -get_be16(head, SFNT_HEAD_YMIN_OFFSET); ys[4] = get_be16(hhea, SFNT_HHEA_LEADING_OFFSET); // now do some cleanup, to ensure y[max,min] are really that if (ys[0] > ys[1]) { ys[0] = ys[1]; } if (ys[3] < ys[2]) { ys[3] = ys[2]; } for (int i = 0; i < 5; i++) { pts[i].set(0, SkIntToScalar(ys[i]) / upem); } sk_free(hhea); sk_free(head); return true; } void SkScalerContext_Mac::generateFontMetrics(SkPaint::FontMetrics* mx, SkPaint::FontMetrics* my) { SkPoint pts[5]; if (!init_vertical_metrics(fRec.fFontID, pts)) { // these are not as accurate as init_vertical_metrics :( ATSFontMetrics metrics; ATSFontGetVerticalMetrics(fRec.fFontID, kATSOptionFlagsDefault, &metrics); pts[0].set(0, -SkFloatToScalar(metrics.ascent)); pts[1].set(0, -SkFloatToScalar(metrics.ascent)); pts[2].set(0, -SkFloatToScalar(metrics.descent)); pts[3].set(0, -SkFloatToScalar(metrics.descent)); pts[4].set(0, SkFloatToScalar(metrics.leading)); //+ or -? } SkMatrix m; fRec.getSingleMatrix(&m); m.mapPoints(pts, 5); if (mx) { mx->fTop = pts[0].fX; mx->fAscent = pts[1].fX; mx->fDescent = pts[2].fX; mx->fBottom = pts[3].fX; mx->fLeading = pts[4].fX; // FIXME: mx->fAvgCharWidth = 0; mx->fXMin = 0; mx->fXMax = 0; mx->fXHeight = 0; } if (my) { my->fTop = pts[0].fY; my->fAscent = pts[1].fY; my->fDescent = pts[2].fY; my->fBottom = pts[3].fY; my->fLeading = pts[4].fY; // FIXME: my->fAvgCharWidth = 0; my->fXMin = 0; my->fXMax = 0; my->fXHeight = 0; } } void SkScalerContext_Mac::generatePath(const SkGlyph& glyph, SkPath* path) { SkAutoMutexAcquire ac(gFTMutex); OSStatus err,result; err = ::ATSUGlyphGetCubicPaths( fStyle,glyph.fID, &SkScalerContext_Mac::MoveTo, &SkScalerContext_Mac::Line, &SkScalerContext_Mac::Curve, &SkScalerContext_Mac::Close, path,&result); SkASSERT(err == noErr); } OSStatus SkScalerContext_Mac::MoveTo(const Float32Point *pt, void *cb) { reinterpret_cast(cb)->moveTo(F32PtToSkPoint(*pt)); return noErr; } OSStatus SkScalerContext_Mac::Line(const Float32Point *pt, void *cb) { reinterpret_cast(cb)->lineTo(F32PtToSkPoint(*pt)); return noErr; } OSStatus SkScalerContext_Mac::Curve(const Float32Point *pt1, const Float32Point *pt2, const Float32Point *pt3, void *cb) { reinterpret_cast(cb)->cubicTo(F32PtToSkPoint(*pt1), F32PtToSkPoint(*pt2), F32PtToSkPoint(*pt3)); return noErr; } OSStatus SkScalerContext_Mac::Close(void *cb) { reinterpret_cast(cb)->close(); return noErr; } #pragma mark - void SkFontHost::Serialize(const SkTypeface* face, SkWStream* stream) { SkASSERT(!"SkFontHost::Serialize unimplemented"); } SkTypeface* SkFontHost::Deserialize(SkStream* stream) { SkASSERT(!"SkFontHost::Deserialize unimplemented"); return NULL; } SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) { return NULL; } SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) { return NULL; } SkScalerContext* SkFontHost::CreateScalerContext(const SkDescriptor* desc) { return new SkScalerContext_Mac(desc); } uint32_t SkFontHost::NextLogicalFont(uint32_t fontID) { uint32_t newFontID = find_default_fontID(); if (newFontID == fontID) { newFontID = 0; } return newFontID; } SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace, const char familyName[], SkTypeface::Style style) { // todo: we don't know how to respect style bits if (NULL == familyName && NULL != familyFace) { familyFace->ref(); return const_cast(familyFace); } else { return CreateTypeface_(familyName, style); } } size_t SkFontHost::ShouldPurgeFontCache(size_t sizeAllocatedSoFar) { if (sizeAllocatedSoFar > FONT_CACHE_MEMORY_BUDGET) return sizeAllocatedSoFar - FONT_CACHE_MEMORY_BUDGET; else return 0; // nothing to do } int SkFontHost::ComputeGammaFlag(const SkPaint& paint) { return 0; } void SkFontHost::GetGammaTables(const uint8_t* tables[2]) { tables[0] = NULL; // black gamma (e.g. exp=1.4) tables[1] = NULL; // white gamma (e.g. exp= 1/1.4) } /////////////////////////////////////////////////////////////////////////////// struct SkSFNTHeader { uint32_t fVersion; uint16_t fNumTables; uint16_t fSearchRange; uint16_t fEntrySelector; uint16_t fRangeShift; }; struct SkSFNTDirEntry { uint32_t fTag; uint32_t fChecksum; uint32_t fOffset; uint32_t fLength; }; struct SfntHeader { SfntHeader(SkFontID fontID, bool needDir) : fCount(0), fData(NULL) { ByteCount size; if (ATSFontGetTableDirectory(fontID, 0, NULL, &size)) { return; } SkAutoMalloc storage(size); SkSFNTHeader* header = reinterpret_cast(storage.get()); if (ATSFontGetTableDirectory(fontID, size, header, &size)) { return; } fCount = SkEndian_SwapBE16(header->fNumTables); fData = header; storage.detach(); } ~SfntHeader() { sk_free(fData); } int count() const { return fCount; } const SkSFNTDirEntry* entries() const { return reinterpret_cast (reinterpret_cast(fData) + sizeof(SkSFNTHeader)); } private: int fCount; void* fData; }; int SkFontHost::CountTables(SkFontID fontID) { SfntHeader header(fontID, false); return header.count(); } int SkFontHost::GetTableTags(SkFontID fontID, SkFontTableTag tags[]) { SfntHeader header(fontID, true); int count = header.count(); const SkSFNTDirEntry* entry = header.entries(); for (int i = 0; i < count; i++) { tags[i] = SkEndian_SwapBE32(entry[i].fTag); } return count; } size_t SkFontHost::GetTableSize(SkFontID fontID, SkFontTableTag tag) { ByteCount size; if (ATSFontGetTable(fontID, tag, 0, 0, NULL, &size)) { return 0; } return size; } size_t SkFontHost::GetTableData(SkFontID fontID, SkFontTableTag tag, size_t offset, size_t length, void* data) { ByteCount size; if (ATSFontGetTable(fontID, tag, offset, length, data, &size)) { return 0; } if (offset >= size) { return 0; } if (offset + length > size) { length = size - offset; } return length; }