blob: 1d40a42a94ed3bf99951a36b5d88f62079a25436 [file] [log] [blame]
reed@android.comfeda2f92010-05-19 13:47:05 +00001/*
2 ** Copyright 2006, The Android Open Source Project
3 **
4 ** Licensed under the Apache License, Version 2.0 (the "License");
5 ** you may not use this file except in compliance with the License.
6 ** You may obtain a copy of the License at
7 **
8 ** http://www.apache.org/licenses/LICENSE-2.0
9 **
10 ** Unless required by applicable law or agreed to in writing, software
11 ** distributed under the License is distributed on an "AS IS" BASIS,
12 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 ** See the License for the specific language governing permissions and
14 ** limitations under the License.
15*/
16
17#include <Carbon/Carbon.h>
18#include "SkFontHost.h"
19#include "SkDescriptor.h"
20#include "SkEndian.h"
21#include "SkFloatingPoint.h"
22#include "SkPaint.h"
23#include "SkPoint.h"
24
25// Give 1MB font cache budget
26#define FONT_CACHE_MEMORY_BUDGET (1024 * 1024)
27
28const char* gDefaultfont = "Arial"; // hard code for now
29static SkMutex gFTMutex;
30
31static inline SkPoint F32PtToSkPoint(const Float32Point p) {
32 SkPoint sp = { SkFloatToScalar(p.x), SkFloatToScalar(p.y) };
33 return sp;
34}
35
36static inline uint32_t _rotl(uint32_t v, uint32_t r) {
37 return (v << r | v >> (32 - r));
38}
39
40class SkTypeface_Mac : public SkTypeface {
41public:
42 SkTypeface_Mac(SkTypeface::Style style, uint32_t id)
43 : SkTypeface(style, id) {}
44};
45
46#pragma mark -
47
48static uint32_t find_from_name(const char name[]) {
49 CFStringRef str = CFStringCreateWithCString(NULL, name,
50 kCFStringEncodingUTF8);
51 uint32_t fontID = ::ATSFontFindFromName(str, kATSOptionFlagsDefault);
52 CFRelease(str);
53 return fontID;
54}
55
56static uint32_t find_default_fontID() {
57 static const char* gDefaultNames[] = { "Arial", "Tahoma", "Helvetica" };
58
59 uint32_t fontID;
60 for (size_t i = 0; i < SK_ARRAY_COUNT(gDefaultNames); i++) {
61 fontID = find_from_name(gDefaultNames[i]);
62 if (fontID) {
63 return fontID;
64 }
65 }
66 sk_throw();
67 return 0;
68}
69
70static SkTypeface* CreateTypeface_(const char name[], SkTypeface::Style style) {
71 uint32_t fontID = 0;
72 if (NULL != name) {
73 fontID = find_from_name(name);
74 }
75 if (0 == fontID) {
76 fontID = find_default_fontID();
77 }
78 // we lie (for now) and report that we found the exact style bits
79 return new SkTypeface_Mac(style, fontID);
80}
81
82#pragma mark -
83
84class SkScalerContext_Mac : public SkScalerContext {
85public:
86 SkScalerContext_Mac(const SkDescriptor* desc);
87 virtual ~SkScalerContext_Mac();
88
89protected:
ctguil@chromium.org0bc7bf52011-03-04 19:04:57 +000090 virtual unsigned generateGlyphCount();
reed@android.comfeda2f92010-05-19 13:47:05 +000091 virtual uint16_t generateCharToGlyph(SkUnichar uni);
92 virtual void generateAdvance(SkGlyph* glyph);
93 virtual void generateMetrics(SkGlyph* glyph);
94 virtual void generateImage(const SkGlyph& glyph);
95 virtual void generatePath(const SkGlyph& glyph, SkPath* path);
96 virtual void generateFontMetrics(SkPaint::FontMetrics* mX, SkPaint::FontMetrics* mY);
97
98private:
99 ATSUTextLayout fLayout;
100 ATSUStyle fStyle;
101 CGColorSpaceRef fGrayColorSpace;
102 CGAffineTransform fTransform;
103
104 static OSStatus MoveTo(const Float32Point *pt, void *cb);
105 static OSStatus Line(const Float32Point *pt, void *cb);
106 static OSStatus Curve(const Float32Point *pt1, const Float32Point *pt2, const Float32Point *pt3, void *cb);
107 static OSStatus Close(void *cb);
108};
109
110void SkFontHost::FilterRec(SkScalerContext::Rec* rec) {
111 // we only support 2 levels of hinting
112 SkPaint::Hinting h = rec->getHinting();
113 if (SkPaint::kSlight_Hinting == h) {
114 h = SkPaint::kNo_Hinting;
115 } else if (SkPaint::kFull_Hinting == h) {
116 h = SkPaint::kNormal_Hinting;
117 }
118 rec->setHinting(h);
119
120 // we don't support LCD text
121 if (SkMask::FormatIsLCD((SkMask::Format)rec->fMaskFormat)) {
122 rec->fMaskFormat = SkMask::kA8_Format;
123 }
124}
125
126SkScalerContext_Mac::SkScalerContext_Mac(const SkDescriptor* desc)
127 : SkScalerContext(desc), fLayout(0), fStyle(0)
128{
129 SkAutoMutexAcquire ac(gFTMutex);
130 OSStatus err;
131
132 err = ::ATSUCreateStyle(&fStyle);
133 SkASSERT(0 == err);
134
135 SkMatrix m;
136 fRec.getSingleMatrix(&m);
137
138 fTransform = CGAffineTransformMake(SkScalarToFloat(m[SkMatrix::kMScaleX]),
139 SkScalarToFloat(m[SkMatrix::kMSkewX]),
140 SkScalarToFloat(m[SkMatrix::kMSkewY]),
141 SkScalarToFloat(m[SkMatrix::kMScaleY]),
142 SkScalarToFloat(m[SkMatrix::kMTransX]),
143 SkScalarToFloat(m[SkMatrix::kMTransY]));
144
145 ATSStyleRenderingOptions renderOpts = kATSStyleApplyAntiAliasing;
146 switch (fRec.getHinting()) {
147 case SkPaint::kNo_Hinting:
148 case SkPaint::kSlight_Hinting:
149 renderOpts |= kATSStyleNoHinting;
150 break;
151 case SkPaint::kNormal_Hinting:
152 case SkPaint::kFull_Hinting:
153 renderOpts |= kATSStyleApplyHints;
154 break;
155 }
156
157 ATSUFontID fontID = FMGetFontFromATSFontRef(fRec.fFontID);
158 // we put everything in the matrix, so our pt size is just 1.0
159 Fixed fixedSize = SK_Fixed1;
160 static const ATSUAttributeTag tags[] = {
161 kATSUFontTag, kATSUSizeTag, kATSUFontMatrixTag, kATSUStyleRenderingOptionsTag
162 };
163 static const ByteCount sizes[] = {
164 sizeof(fontID), sizeof(fixedSize), sizeof(fTransform), sizeof(renderOpts)
165 };
166 const ATSUAttributeValuePtr values[] = {
167 &fontID, &fixedSize, &fTransform, &renderOpts
168 };
169 err = ::ATSUSetAttributes(fStyle, SK_ARRAY_COUNT(tags),
170 tags, sizes, values);
171 SkASSERT(0 == err);
172
173 err = ::ATSUCreateTextLayout(&fLayout);
174 SkASSERT(0 == err);
175
176 fGrayColorSpace = ::CGColorSpaceCreateDeviceGray();
177}
178
179SkScalerContext_Mac::~SkScalerContext_Mac() {
180 ::CGColorSpaceRelease(fGrayColorSpace);
181 ::ATSUDisposeTextLayout(fLayout);
182 ::ATSUDisposeStyle(fStyle);
183}
184
185// man, we need to consider caching this, since it is just dependent on
186// fFontID, and not on any of the other settings like matrix or flags
ctguil@chromium.org0bc7bf52011-03-04 19:04:57 +0000187unsigned SkScalerContext_Mac::generateGlyphCount() {
reed@android.comfeda2f92010-05-19 13:47:05 +0000188 // The 'maxp' table stores the number of glyphs a offset 4, in 2 bytes
189 uint16_t numGlyphs;
190 if (SkFontHost::GetTableData(fRec.fFontID,
191 SkSetFourByteTag('m', 'a', 'x', 'p'),
192 4, 2, &numGlyphs) != 2) {
193 return 0xFFFF;
194 }
195 return SkEndian_SwapBE16(numGlyphs);
196}
197
198uint16_t SkScalerContext_Mac::generateCharToGlyph(SkUnichar uni)
199{
200 SkAutoMutexAcquire ac(gFTMutex);
201
202 OSStatus err;
203 UniChar achar = uni;
204 err = ::ATSUSetTextPointerLocation(fLayout,&achar,0,1,1);
205 err = ::ATSUSetRunStyle(fLayout,fStyle,kATSUFromTextBeginning,kATSUToTextEnd);
206
207 ATSLayoutRecord *layoutPtr;
208 ItemCount count;
209 ATSGlyphRef glyph;
210
211 err = ::ATSUDirectGetLayoutDataArrayPtrFromTextLayout(fLayout,0,kATSUDirectDataLayoutRecordATSLayoutRecordCurrent,(void**)&layoutPtr,&count);
212 glyph = layoutPtr->glyphID;
213 ::ATSUDirectReleaseLayoutDataArrayPtr(NULL,kATSUDirectDataLayoutRecordATSLayoutRecordCurrent,(void**)&layoutPtr);
214 return glyph;
215}
216
217static void set_glyph_metrics_on_error(SkGlyph* glyph) {
218 glyph->fRsbDelta = 0;
219 glyph->fLsbDelta = 0;
220 glyph->fWidth = 0;
221 glyph->fHeight = 0;
222 glyph->fTop = 0;
223 glyph->fLeft = 0;
224 glyph->fAdvanceX = 0;
225 glyph->fAdvanceY = 0;
226}
227
228void SkScalerContext_Mac::generateAdvance(SkGlyph* glyph) {
229 this->generateMetrics(glyph);
230}
231
232void SkScalerContext_Mac::generateMetrics(SkGlyph* glyph) {
233 GlyphID glyphID = glyph->getGlyphID(fBaseGlyphCount);
234 ATSGlyphScreenMetrics screenMetrics;
235 ATSGlyphIdealMetrics idealMetrics;
236
237 OSStatus err = ATSUGlyphGetScreenMetrics(fStyle, 1, &glyphID, 0, true, true,
238 &screenMetrics);
239 if (noErr != err) {
240 set_glyph_metrics_on_error(glyph);
241 return;
242 }
243 err = ATSUGlyphGetIdealMetrics(fStyle, 1, &glyphID, 0, &idealMetrics);
244 if (noErr != err) {
245 set_glyph_metrics_on_error(glyph);
246 return;
247 }
248
agl@chromium.orga2c71cb2010-06-17 20:49:17 +0000249 if ((fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) == 0) {
reed@android.comfeda2f92010-05-19 13:47:05 +0000250 glyph->fAdvanceX = SkFloatToFixed(screenMetrics.deviceAdvance.x);
251 glyph->fAdvanceY = -SkFloatToFixed(screenMetrics.deviceAdvance.y);
252 } else {
253 glyph->fAdvanceX = SkFloatToFixed(idealMetrics.advance.x);
254 glyph->fAdvanceY = -SkFloatToFixed(idealMetrics.advance.y);
255 }
256
257 // specify an extra 1-pixel border, go tive CG room for its antialiasing
258 // i.e. without this, I was seeing some edges chopped off!
259 glyph->fWidth = screenMetrics.width + 2;
260 glyph->fHeight = screenMetrics.height + 2;
261 glyph->fLeft = sk_float_round2int(screenMetrics.topLeft.x) - 1;
262 glyph->fTop = -sk_float_round2int(screenMetrics.topLeft.y) - 1;
263}
264
265void SkScalerContext_Mac::generateImage(const SkGlyph& glyph)
266{
267 SkAutoMutexAcquire ac(gFTMutex);
268 SkASSERT(fLayout);
269
270 sk_bzero(glyph.fImage, glyph.fHeight * glyph.rowBytes());
271 CGContextRef contextRef = ::CGBitmapContextCreate(glyph.fImage,
272 glyph.fWidth, glyph.fHeight, 8,
273 glyph.rowBytes(), fGrayColorSpace,
274 kCGImageAlphaNone);
275 if (!contextRef) {
276 SkASSERT(false);
277 return;
278 }
279
280 ::CGContextSetGrayFillColor(contextRef, 1.0, 1.0);
281 ::CGContextSetTextDrawingMode(contextRef, kCGTextFill);
282
283 CGGlyph glyphID = glyph.getGlyphID(fBaseGlyphCount);
284 CGFontRef fontRef = CGFontCreateWithPlatformFont(&fRec.fFontID);
285 CGContextSetFont(contextRef, fontRef);
286 CGContextSetFontSize(contextRef, 1);
287 CGContextSetTextMatrix(contextRef, fTransform);
288 CGContextShowGlyphsAtPoint(contextRef, -glyph.fLeft,
289 glyph.fTop + glyph.fHeight, &glyphID, 1);
290
291 ::CGContextRelease(contextRef);
292}
293
294#if 0
295static void convert_metrics(SkPaint::FontMetrics* dst,
296 const ATSFontMetrics& src) {
297 dst->fTop = -SkFloatToScalar(src.ascent);
298 dst->fAscent = -SkFloatToScalar(src.ascent);
299 dst->fDescent = SkFloatToScalar(src.descent);
300 dst->fBottom = SkFloatToScalar(src.descent);
301 dst->fLeading = SkFloatToScalar(src.leading);
302}
303#endif
304
305static void* get_font_table(ATSFontRef fontID, uint32_t tag) {
306 ByteCount size;
307 OSStatus err = ATSFontGetTable(fontID, tag, 0, 0, NULL, &size);
308 if (err) {
309 return NULL;
310 }
311 void* data = sk_malloc_throw(size);
312 err = ATSFontGetTable(fontID, tag, 0, size, data, &size);
313 if (err) {
314 sk_free(data);
315 data = NULL;
316 }
317 return data;
318}
319
320static int get_be16(const void* data, size_t offset) {
321 const char* ptr = reinterpret_cast<const char*>(data);
322 uint16_t value = *reinterpret_cast<const uint16_t*>(ptr + offset);
323 int n = SkEndian_SwapBE16(value);
324 // now force it to be signed
325 return n << 16 >> 16;
326}
327
328#define SFNT_HEAD_UPEM_OFFSET 18
329#define SFNT_HEAD_YMIN_OFFSET 38
330#define SFNT_HEAD_YMAX_OFFSET 42
331#define SFNT_HEAD_STYLE_OFFSET 44
332
333#define SFNT_HHEA_ASCENT_OFFSET 4
334#define SFNT_HHEA_DESCENT_OFFSET 6
335#define SFNT_HHEA_LEADING_OFFSET 8
336
337static bool init_vertical_metrics(ATSFontRef font, SkPoint pts[5]) {
338 void* head = get_font_table(font, 'head');
339 if (NULL == head) {
340 return false;
341 }
342 void* hhea = get_font_table(font, 'hhea');
343 if (NULL == hhea) {
344 sk_free(head);
345 return false;
346 }
347
348 int upem = get_be16(head, SFNT_HEAD_UPEM_OFFSET);
349 int ys[5];
350
351 ys[0] = -get_be16(head, SFNT_HEAD_YMAX_OFFSET);
352 ys[1] = -get_be16(hhea, SFNT_HHEA_ASCENT_OFFSET);
353 ys[2] = -get_be16(hhea, SFNT_HHEA_DESCENT_OFFSET);
354 ys[3] = -get_be16(head, SFNT_HEAD_YMIN_OFFSET);
355 ys[4] = get_be16(hhea, SFNT_HHEA_LEADING_OFFSET);
356
357 // now do some cleanup, to ensure y[max,min] are really that
358 if (ys[0] > ys[1]) {
359 ys[0] = ys[1];
360 }
361 if (ys[3] < ys[2]) {
362 ys[3] = ys[2];
363 }
364
365 for (int i = 0; i < 5; i++) {
366 pts[i].set(0, SkIntToScalar(ys[i]) / upem);
367 }
368
369 sk_free(hhea);
370 sk_free(head);
371 return true;
372}
373
374void SkScalerContext_Mac::generateFontMetrics(SkPaint::FontMetrics* mx,
375 SkPaint::FontMetrics* my) {
376 SkPoint pts[5];
377
378 if (!init_vertical_metrics(fRec.fFontID, pts)) {
379 // these are not as accurate as init_vertical_metrics :(
380 ATSFontMetrics metrics;
381 ATSFontGetVerticalMetrics(fRec.fFontID, kATSOptionFlagsDefault,
382 &metrics);
383 pts[0].set(0, -SkFloatToScalar(metrics.ascent));
384 pts[1].set(0, -SkFloatToScalar(metrics.ascent));
385 pts[2].set(0, -SkFloatToScalar(metrics.descent));
386 pts[3].set(0, -SkFloatToScalar(metrics.descent));
387 pts[4].set(0, SkFloatToScalar(metrics.leading)); //+ or -?
388 }
389
390 SkMatrix m;
391 fRec.getSingleMatrix(&m);
392 m.mapPoints(pts, 5);
393
394 if (mx) {
395 mx->fTop = pts[0].fX;
396 mx->fAscent = pts[1].fX;
397 mx->fDescent = pts[2].fX;
398 mx->fBottom = pts[3].fX;
399 mx->fLeading = pts[4].fX;
400 // FIXME:
401 mx->fAvgCharWidth = 0;
402 mx->fXMin = 0;
403 mx->fXMax = 0;
404 mx->fXHeight = 0;
405 }
406 if (my) {
407 my->fTop = pts[0].fY;
408 my->fAscent = pts[1].fY;
409 my->fDescent = pts[2].fY;
410 my->fBottom = pts[3].fY;
411 my->fLeading = pts[4].fY;
412 // FIXME:
413 my->fAvgCharWidth = 0;
414 my->fXMin = 0;
415 my->fXMax = 0;
416 my->fXHeight = 0;
417 }
418}
419
420void SkScalerContext_Mac::generatePath(const SkGlyph& glyph, SkPath* path)
421{
422 SkAutoMutexAcquire ac(gFTMutex);
423 OSStatus err,result;
424
425 err = ::ATSUGlyphGetCubicPaths(
426 fStyle,glyph.fID,
427 &SkScalerContext_Mac::MoveTo,
428 &SkScalerContext_Mac::Line,
429 &SkScalerContext_Mac::Curve,
430 &SkScalerContext_Mac::Close,
431 path,&result);
432 SkASSERT(err == noErr);
433}
434
435OSStatus SkScalerContext_Mac::MoveTo(const Float32Point *pt, void *cb)
436{
437 reinterpret_cast<SkPath*>(cb)->moveTo(F32PtToSkPoint(*pt));
438 return noErr;
439}
440
441OSStatus SkScalerContext_Mac::Line(const Float32Point *pt, void *cb)
442{
443 reinterpret_cast<SkPath*>(cb)->lineTo(F32PtToSkPoint(*pt));
444 return noErr;
445}
446
447OSStatus SkScalerContext_Mac::Curve(const Float32Point *pt1,
448 const Float32Point *pt2,
449 const Float32Point *pt3, void *cb)
450{
451 reinterpret_cast<SkPath*>(cb)->cubicTo(F32PtToSkPoint(*pt1),
452 F32PtToSkPoint(*pt2),
453 F32PtToSkPoint(*pt3));
454 return noErr;
455}
456
457OSStatus SkScalerContext_Mac::Close(void *cb)
458{
459 reinterpret_cast<SkPath*>(cb)->close();
460 return noErr;
461}
462
463#pragma mark -
464
465void SkFontHost::Serialize(const SkTypeface* face, SkWStream* stream) {
466 SkASSERT(!"SkFontHost::Serialize unimplemented");
467}
468
469SkTypeface* SkFontHost::Deserialize(SkStream* stream) {
470 SkASSERT(!"SkFontHost::Deserialize unimplemented");
471 return NULL;
472}
473
474SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) {
475 return NULL;
476}
477
478SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) {
479 return NULL;
480}
481
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000482// static
vandebo@chromium.orgc48b2b32011-02-02 02:11:22 +0000483SkAdvancedTypefaceMetrics* SkFontHost::GetAdvancedTypefaceMetrics(
484 uint32_t fontID, bool perGlyphInfo) {
485 SkASSERT(!"SkFontHost::GetAdvancedTypefaceMetrics unimplemented");
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000486 return NULL;
487}
488
reed@android.comfeda2f92010-05-19 13:47:05 +0000489SkScalerContext* SkFontHost::CreateScalerContext(const SkDescriptor* desc) {
490 return new SkScalerContext_Mac(desc);
491}
492
493uint32_t SkFontHost::NextLogicalFont(uint32_t fontID) {
494 uint32_t newFontID = find_default_fontID();
495 if (newFontID == fontID) {
496 newFontID = 0;
497 }
498 return newFontID;
499}
500
501SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace,
502 const char familyName[],
503 const void* data, size_t bytelength,
504 SkTypeface::Style style) {
505 // todo: we don't know how to respect style bits
506 if (NULL == familyName && NULL != familyFace) {
507 familyFace->ref();
508 return const_cast<SkTypeface*>(familyFace);
509 } else {
510 return CreateTypeface_(familyName, style);
511 }
512}
513
514size_t SkFontHost::ShouldPurgeFontCache(size_t sizeAllocatedSoFar) {
515 if (sizeAllocatedSoFar > FONT_CACHE_MEMORY_BUDGET)
516 return sizeAllocatedSoFar - FONT_CACHE_MEMORY_BUDGET;
517 else
518 return 0; // nothing to do
519}
520
521int SkFontHost::ComputeGammaFlag(const SkPaint& paint) {
522 return 0;
523}
524
525void SkFontHost::GetGammaTables(const uint8_t* tables[2]) {
526 tables[0] = NULL; // black gamma (e.g. exp=1.4)
527 tables[1] = NULL; // white gamma (e.g. exp= 1/1.4)
528}
529
530///////////////////////////////////////////////////////////////////////////////
531
532struct SkSFNTHeader {
533 uint32_t fVersion;
534 uint16_t fNumTables;
535 uint16_t fSearchRange;
536 uint16_t fEntrySelector;
537 uint16_t fRangeShift;
538};
539
540struct SkSFNTDirEntry {
541 uint32_t fTag;
542 uint32_t fChecksum;
543 uint32_t fOffset;
544 uint32_t fLength;
545};
546
547struct SfntHeader {
548 SfntHeader(SkFontID fontID, bool needDir) : fCount(0), fData(NULL) {
549 ByteCount size;
550 if (ATSFontGetTableDirectory(fontID, 0, NULL, &size)) {
551 return;
552 }
553
554 SkAutoMalloc storage(size);
555 SkSFNTHeader* header = reinterpret_cast<SkSFNTHeader*>(storage.get());
556 if (ATSFontGetTableDirectory(fontID, size, header, &size)) {
557 return;
558 }
559
560 fCount = SkEndian_SwapBE16(header->fNumTables);
561 fData = header;
562 storage.detach();
563 }
564
565 ~SfntHeader() {
566 sk_free(fData);
567 }
568
569 int count() const { return fCount; }
570 const SkSFNTDirEntry* entries() const {
571 return reinterpret_cast<const SkSFNTDirEntry*>
572 (reinterpret_cast<char*>(fData) + sizeof(SkSFNTHeader));
573 }
574
575private:
576 int fCount;
577 void* fData;
578};
579
580int SkFontHost::CountTables(SkFontID fontID) {
581 SfntHeader header(fontID, false);
582 return header.count();
583}
584
585int SkFontHost::GetTableTags(SkFontID fontID, SkFontTableTag tags[]) {
586 SfntHeader header(fontID, true);
587 int count = header.count();
588 const SkSFNTDirEntry* entry = header.entries();
589 for (int i = 0; i < count; i++) {
590 tags[i] = SkEndian_SwapBE32(entry[i].fTag);
591 }
592 return count;
593}
594
595size_t SkFontHost::GetTableSize(SkFontID fontID, SkFontTableTag tag) {
596 ByteCount size;
597 if (ATSFontGetTable(fontID, tag, 0, 0, NULL, &size)) {
598 return 0;
599 }
600 return size;
601}
602
603size_t SkFontHost::GetTableData(SkFontID fontID, SkFontTableTag tag,
604 size_t offset, size_t length, void* data) {
605 ByteCount size;
606 if (ATSFontGetTable(fontID, tag, offset, length, data, &size)) {
607 return 0;
608 }
609 if (offset >= size) {
610 return 0;
611 }
612 if (offset + length > size) {
613 length = size - offset;
614 }
615 return length;
616}
617