michael@0: /* michael@0: * Copyright © 2012,2013 Mozilla Foundation. michael@0: * Copyright © 2012,2013 Google, Inc. michael@0: * michael@0: * This is part of HarfBuzz, a text shaping library. michael@0: * michael@0: * Permission is hereby granted, without written agreement and without michael@0: * license or royalty fees, to use, copy, modify, and distribute this michael@0: * software and its documentation for any purpose, provided that the michael@0: * above copyright notice and the following two paragraphs appear in michael@0: * all copies of this software. michael@0: * michael@0: * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR michael@0: * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES michael@0: * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN michael@0: * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH michael@0: * DAMAGE. michael@0: * michael@0: * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, michael@0: * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND michael@0: * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS michael@0: * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO michael@0: * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. michael@0: * michael@0: * Mozilla Author(s): Jonathan Kew michael@0: * Google Author(s): Behdad Esfahbod michael@0: */ michael@0: michael@0: #define HB_SHAPER coretext michael@0: #include "hb-shaper-impl-private.hh" michael@0: michael@0: #include "hb-coretext.h" michael@0: michael@0: michael@0: #ifndef HB_DEBUG_CORETEXT michael@0: #define HB_DEBUG_CORETEXT (HB_DEBUG+0) michael@0: #endif michael@0: michael@0: michael@0: HB_SHAPER_DATA_ENSURE_DECLARE(coretext, face) michael@0: HB_SHAPER_DATA_ENSURE_DECLARE(coretext, font) michael@0: michael@0: michael@0: /* michael@0: * shaper face data michael@0: */ michael@0: michael@0: struct hb_coretext_shaper_face_data_t { michael@0: CGFontRef cg_font; michael@0: }; michael@0: michael@0: static void michael@0: release_data (void *info, const void *data, size_t size) michael@0: { michael@0: assert (hb_blob_get_length ((hb_blob_t *) info) == size && michael@0: hb_blob_get_data ((hb_blob_t *) info, NULL) == data); michael@0: michael@0: hb_blob_destroy ((hb_blob_t *) info); michael@0: } michael@0: michael@0: hb_coretext_shaper_face_data_t * michael@0: _hb_coretext_shaper_face_data_create (hb_face_t *face) michael@0: { michael@0: hb_coretext_shaper_face_data_t *data = (hb_coretext_shaper_face_data_t *) calloc (1, sizeof (hb_coretext_shaper_face_data_t)); michael@0: if (unlikely (!data)) michael@0: return NULL; michael@0: michael@0: hb_blob_t *blob = hb_face_reference_blob (face); michael@0: unsigned int blob_length; michael@0: const char *blob_data = hb_blob_get_data (blob, &blob_length); michael@0: if (unlikely (!blob_length)) michael@0: DEBUG_MSG (CORETEXT, face, "Face has empty blob"); michael@0: michael@0: CGDataProviderRef provider = CGDataProviderCreateWithData (blob, blob_data, blob_length, &release_data); michael@0: data->cg_font = CGFontCreateWithDataProvider (provider); michael@0: CGDataProviderRelease (provider); michael@0: michael@0: if (unlikely (!data->cg_font)) { michael@0: DEBUG_MSG (CORETEXT, face, "Face CGFontCreateWithDataProvider() failed"); michael@0: free (data); michael@0: return NULL; michael@0: } michael@0: michael@0: return data; michael@0: } michael@0: michael@0: void michael@0: _hb_coretext_shaper_face_data_destroy (hb_coretext_shaper_face_data_t *data) michael@0: { michael@0: CFRelease (data->cg_font); michael@0: free (data); michael@0: } michael@0: michael@0: CGFontRef michael@0: hb_coretext_face_get_cg_font (hb_face_t *face) michael@0: { michael@0: if (unlikely (!hb_coretext_shaper_face_data_ensure (face))) return NULL; michael@0: hb_coretext_shaper_face_data_t *face_data = HB_SHAPER_DATA_GET (face); michael@0: return face_data->cg_font; michael@0: } michael@0: michael@0: michael@0: /* michael@0: * shaper font data michael@0: */ michael@0: michael@0: struct hb_coretext_shaper_font_data_t { michael@0: CTFontRef ct_font; michael@0: }; michael@0: michael@0: hb_coretext_shaper_font_data_t * michael@0: _hb_coretext_shaper_font_data_create (hb_font_t *font) michael@0: { michael@0: if (unlikely (!hb_coretext_shaper_face_data_ensure (font->face))) return NULL; michael@0: michael@0: hb_coretext_shaper_font_data_t *data = (hb_coretext_shaper_font_data_t *) calloc (1, sizeof (hb_coretext_shaper_font_data_t)); michael@0: if (unlikely (!data)) michael@0: return NULL; michael@0: michael@0: hb_face_t *face = font->face; michael@0: hb_coretext_shaper_face_data_t *face_data = HB_SHAPER_DATA_GET (face); michael@0: michael@0: data->ct_font = CTFontCreateWithGraphicsFont (face_data->cg_font, font->y_scale, NULL, NULL); michael@0: if (unlikely (!data->ct_font)) { michael@0: DEBUG_MSG (CORETEXT, font, "Font CTFontCreateWithGraphicsFont() failed"); michael@0: free (data); michael@0: return NULL; michael@0: } michael@0: michael@0: return data; michael@0: } michael@0: michael@0: void michael@0: _hb_coretext_shaper_font_data_destroy (hb_coretext_shaper_font_data_t *data) michael@0: { michael@0: CFRelease (data->ct_font); michael@0: free (data); michael@0: } michael@0: michael@0: michael@0: /* michael@0: * shaper shape_plan data michael@0: */ michael@0: michael@0: struct hb_coretext_shaper_shape_plan_data_t {}; michael@0: michael@0: hb_coretext_shaper_shape_plan_data_t * michael@0: _hb_coretext_shaper_shape_plan_data_create (hb_shape_plan_t *shape_plan HB_UNUSED, michael@0: const hb_feature_t *user_features HB_UNUSED, michael@0: unsigned int num_user_features HB_UNUSED) michael@0: { michael@0: return (hb_coretext_shaper_shape_plan_data_t *) HB_SHAPER_DATA_SUCCEEDED; michael@0: } michael@0: michael@0: void michael@0: _hb_coretext_shaper_shape_plan_data_destroy (hb_coretext_shaper_shape_plan_data_t *data HB_UNUSED) michael@0: { michael@0: } michael@0: michael@0: CTFontRef michael@0: hb_coretext_font_get_ct_font (hb_font_t *font) michael@0: { michael@0: if (unlikely (!hb_coretext_shaper_font_data_ensure (font))) return NULL; michael@0: hb_coretext_shaper_font_data_t *font_data = HB_SHAPER_DATA_GET (font); michael@0: return font_data->ct_font; michael@0: } michael@0: michael@0: michael@0: /* michael@0: * shaper michael@0: */ michael@0: michael@0: struct feature_record_t { michael@0: unsigned int feature; michael@0: unsigned int setting; michael@0: }; michael@0: michael@0: struct active_feature_t { michael@0: feature_record_t rec; michael@0: unsigned int order; michael@0: michael@0: static int cmp (const active_feature_t *a, const active_feature_t *b) { michael@0: return a->rec.feature < b->rec.feature ? -1 : a->rec.feature > b->rec.feature ? 1 : michael@0: a->order < b->order ? -1 : a->order > b->order ? 1 : michael@0: a->rec.setting < b->rec.setting ? -1 : a->rec.setting > b->rec.setting ? 1 : michael@0: 0; michael@0: } michael@0: bool operator== (const active_feature_t *f) { michael@0: return cmp (this, f) == 0; michael@0: } michael@0: }; michael@0: michael@0: struct feature_event_t { michael@0: unsigned int index; michael@0: bool start; michael@0: active_feature_t feature; michael@0: michael@0: static int cmp (const feature_event_t *a, const feature_event_t *b) { michael@0: return a->index < b->index ? -1 : a->index > b->index ? 1 : michael@0: a->start < b->start ? -1 : a->start > b->start ? 1 : michael@0: active_feature_t::cmp (&a->feature, &b->feature); michael@0: } michael@0: }; michael@0: michael@0: struct range_record_t { michael@0: CTFontRef font; michael@0: unsigned int index_first; /* == start */ michael@0: unsigned int index_last; /* == end - 1 */ michael@0: }; michael@0: michael@0: michael@0: /* The following enum members are added in OS X 10.8. */ michael@0: #define kAltHalfWidthTextSelector 6 michael@0: #define kAltProportionalTextSelector 5 michael@0: #define kAlternateHorizKanaOffSelector 1 michael@0: #define kAlternateHorizKanaOnSelector 0 michael@0: #define kAlternateKanaType 34 michael@0: #define kAlternateVertKanaOffSelector 3 michael@0: #define kAlternateVertKanaOnSelector 2 michael@0: #define kCaseSensitiveLayoutOffSelector 1 michael@0: #define kCaseSensitiveLayoutOnSelector 0 michael@0: #define kCaseSensitiveLayoutType 33 michael@0: #define kCaseSensitiveSpacingOffSelector 3 michael@0: #define kCaseSensitiveSpacingOnSelector 2 michael@0: #define kContextualAlternatesOffSelector 1 michael@0: #define kContextualAlternatesOnSelector 0 michael@0: #define kContextualAlternatesType 36 michael@0: #define kContextualLigaturesOffSelector 19 michael@0: #define kContextualLigaturesOnSelector 18 michael@0: #define kContextualSwashAlternatesOffSelector 5 michael@0: #define kContextualSwashAlternatesOnSelector 4 michael@0: #define kDefaultLowerCaseSelector 0 michael@0: #define kDefaultUpperCaseSelector 0 michael@0: #define kHistoricalLigaturesOffSelector 21 michael@0: #define kHistoricalLigaturesOnSelector 20 michael@0: #define kHojoCharactersSelector 12 michael@0: #define kJIS2004CharactersSelector 11 michael@0: #define kLowerCasePetiteCapsSelector 2 michael@0: #define kLowerCaseSmallCapsSelector 1 michael@0: #define kLowerCaseType 37 michael@0: #define kMathematicalGreekOffSelector 11 michael@0: #define kMathematicalGreekOnSelector 10 michael@0: #define kNLCCharactersSelector 13 michael@0: #define kQuarterWidthTextSelector 4 michael@0: #define kScientificInferiorsSelector 4 michael@0: #define kStylisticAltEightOffSelector 17 michael@0: #define kStylisticAltEightOnSelector 16 michael@0: #define kStylisticAltEighteenOffSelector 37 michael@0: #define kStylisticAltEighteenOnSelector 36 michael@0: #define kStylisticAltElevenOffSelector 23 michael@0: #define kStylisticAltElevenOnSelector 22 michael@0: #define kStylisticAltFifteenOffSelector 31 michael@0: #define kStylisticAltFifteenOnSelector 30 michael@0: #define kStylisticAltFiveOffSelector 11 michael@0: #define kStylisticAltFiveOnSelector 10 michael@0: #define kStylisticAltFourOffSelector 9 michael@0: #define kStylisticAltFourOnSelector 8 michael@0: #define kStylisticAltFourteenOffSelector 29 michael@0: #define kStylisticAltFourteenOnSelector 28 michael@0: #define kStylisticAltNineOffSelector 19 michael@0: #define kStylisticAltNineOnSelector 18 michael@0: #define kStylisticAltNineteenOffSelector 39 michael@0: #define kStylisticAltNineteenOnSelector 38 michael@0: #define kStylisticAltOneOffSelector 3 michael@0: #define kStylisticAltOneOnSelector 2 michael@0: #define kStylisticAltSevenOffSelector 15 michael@0: #define kStylisticAltSevenOnSelector 14 michael@0: #define kStylisticAltSeventeenOffSelector 35 michael@0: #define kStylisticAltSeventeenOnSelector 34 michael@0: #define kStylisticAltSixOffSelector 13 michael@0: #define kStylisticAltSixOnSelector 12 michael@0: #define kStylisticAltSixteenOffSelector 33 michael@0: #define kStylisticAltSixteenOnSelector 32 michael@0: #define kStylisticAltTenOffSelector 21 michael@0: #define kStylisticAltTenOnSelector 20 michael@0: #define kStylisticAltThirteenOffSelector 27 michael@0: #define kStylisticAltThirteenOnSelector 26 michael@0: #define kStylisticAltThreeOffSelector 7 michael@0: #define kStylisticAltThreeOnSelector 6 michael@0: #define kStylisticAltTwelveOffSelector 25 michael@0: #define kStylisticAltTwelveOnSelector 24 michael@0: #define kStylisticAltTwentyOffSelector 41 michael@0: #define kStylisticAltTwentyOnSelector 40 michael@0: #define kStylisticAltTwoOffSelector 5 michael@0: #define kStylisticAltTwoOnSelector 4 michael@0: #define kStylisticAlternativesType 35 michael@0: #define kSwashAlternatesOffSelector 3 michael@0: #define kSwashAlternatesOnSelector 2 michael@0: #define kThirdWidthTextSelector 3 michael@0: #define kTraditionalNamesCharactersSelector 14 michael@0: #define kUpperCasePetiteCapsSelector 2 michael@0: #define kUpperCaseSmallCapsSelector 1 michael@0: #define kUpperCaseType 38 michael@0: michael@0: /* Table data courtesy of Apple. */ michael@0: struct feature_mapping_t { michael@0: FourCharCode otFeatureTag; michael@0: uint16_t aatFeatureType; michael@0: uint16_t selectorToEnable; michael@0: uint16_t selectorToDisable; michael@0: } feature_mappings[] = { michael@0: { 'c2pc', kUpperCaseType, kUpperCasePetiteCapsSelector, kDefaultUpperCaseSelector }, michael@0: { 'c2sc', kUpperCaseType, kUpperCaseSmallCapsSelector, kDefaultUpperCaseSelector }, michael@0: { 'calt', kContextualAlternatesType, kContextualAlternatesOnSelector, kContextualAlternatesOffSelector }, michael@0: { 'case', kCaseSensitiveLayoutType, kCaseSensitiveLayoutOnSelector, kCaseSensitiveLayoutOffSelector }, michael@0: { 'clig', kLigaturesType, kContextualLigaturesOnSelector, kContextualLigaturesOffSelector }, michael@0: { 'cpsp', kCaseSensitiveLayoutType, kCaseSensitiveSpacingOnSelector, kCaseSensitiveSpacingOffSelector }, michael@0: { 'cswh', kContextualAlternatesType, kContextualSwashAlternatesOnSelector, kContextualSwashAlternatesOffSelector }, michael@0: { 'dlig', kLigaturesType, kRareLigaturesOnSelector, kRareLigaturesOffSelector }, michael@0: { 'expt', kCharacterShapeType, kExpertCharactersSelector, 16 }, michael@0: { 'frac', kFractionsType, kDiagonalFractionsSelector, kNoFractionsSelector }, michael@0: { 'fwid', kTextSpacingType, kMonospacedTextSelector, 7 }, michael@0: { 'halt', kTextSpacingType, kAltHalfWidthTextSelector, 7 }, michael@0: { 'hist', kLigaturesType, kHistoricalLigaturesOnSelector, kHistoricalLigaturesOffSelector }, michael@0: { 'hkna', kAlternateKanaType, kAlternateHorizKanaOnSelector, kAlternateHorizKanaOffSelector, }, michael@0: { 'hlig', kLigaturesType, kHistoricalLigaturesOnSelector, kHistoricalLigaturesOffSelector }, michael@0: { 'hngl', kTransliterationType, kHanjaToHangulSelector, kNoTransliterationSelector }, michael@0: { 'hojo', kCharacterShapeType, kHojoCharactersSelector, 16 }, michael@0: { 'hwid', kTextSpacingType, kHalfWidthTextSelector, 7 }, michael@0: { 'ital', kItalicCJKRomanType, kCJKItalicRomanOnSelector, kCJKItalicRomanOffSelector }, michael@0: { 'jp04', kCharacterShapeType, kJIS2004CharactersSelector, 16 }, michael@0: { 'jp78', kCharacterShapeType, kJIS1978CharactersSelector, 16 }, michael@0: { 'jp83', kCharacterShapeType, kJIS1983CharactersSelector, 16 }, michael@0: { 'jp90', kCharacterShapeType, kJIS1990CharactersSelector, 16 }, michael@0: { 'liga', kLigaturesType, kCommonLigaturesOnSelector, kCommonLigaturesOffSelector }, michael@0: { 'lnum', kNumberCaseType, kUpperCaseNumbersSelector, 2 }, michael@0: { 'mgrk', kMathematicalExtrasType, kMathematicalGreekOnSelector, kMathematicalGreekOffSelector }, michael@0: { 'nlck', kCharacterShapeType, kNLCCharactersSelector, 16 }, michael@0: { 'onum', kNumberCaseType, kLowerCaseNumbersSelector, 2 }, michael@0: { 'ordn', kVerticalPositionType, kOrdinalsSelector, kNormalPositionSelector }, michael@0: { 'palt', kTextSpacingType, kAltProportionalTextSelector, 7 }, michael@0: { 'pcap', kLowerCaseType, kLowerCasePetiteCapsSelector, kDefaultLowerCaseSelector }, michael@0: { 'pkna', kTextSpacingType, kProportionalTextSelector, 7 }, michael@0: { 'pnum', kNumberSpacingType, kProportionalNumbersSelector, 4 }, michael@0: { 'pwid', kTextSpacingType, kProportionalTextSelector, 7 }, michael@0: { 'qwid', kTextSpacingType, kQuarterWidthTextSelector, 7 }, michael@0: { 'ruby', kRubyKanaType, kRubyKanaOnSelector, kRubyKanaOffSelector }, michael@0: { 'sinf', kVerticalPositionType, kScientificInferiorsSelector, kNormalPositionSelector }, michael@0: { 'smcp', kLowerCaseType, kLowerCaseSmallCapsSelector, kDefaultLowerCaseSelector }, michael@0: { 'smpl', kCharacterShapeType, kSimplifiedCharactersSelector, 16 }, michael@0: { 'ss01', kStylisticAlternativesType, kStylisticAltOneOnSelector, kStylisticAltOneOffSelector }, michael@0: { 'ss02', kStylisticAlternativesType, kStylisticAltTwoOnSelector, kStylisticAltTwoOffSelector }, michael@0: { 'ss03', kStylisticAlternativesType, kStylisticAltThreeOnSelector, kStylisticAltThreeOffSelector }, michael@0: { 'ss04', kStylisticAlternativesType, kStylisticAltFourOnSelector, kStylisticAltFourOffSelector }, michael@0: { 'ss05', kStylisticAlternativesType, kStylisticAltFiveOnSelector, kStylisticAltFiveOffSelector }, michael@0: { 'ss06', kStylisticAlternativesType, kStylisticAltSixOnSelector, kStylisticAltSixOffSelector }, michael@0: { 'ss07', kStylisticAlternativesType, kStylisticAltSevenOnSelector, kStylisticAltSevenOffSelector }, michael@0: { 'ss08', kStylisticAlternativesType, kStylisticAltEightOnSelector, kStylisticAltEightOffSelector }, michael@0: { 'ss09', kStylisticAlternativesType, kStylisticAltNineOnSelector, kStylisticAltNineOffSelector }, michael@0: { 'ss10', kStylisticAlternativesType, kStylisticAltTenOnSelector, kStylisticAltTenOffSelector }, michael@0: { 'ss11', kStylisticAlternativesType, kStylisticAltElevenOnSelector, kStylisticAltElevenOffSelector }, michael@0: { 'ss12', kStylisticAlternativesType, kStylisticAltTwelveOnSelector, kStylisticAltTwelveOffSelector }, michael@0: { 'ss13', kStylisticAlternativesType, kStylisticAltThirteenOnSelector, kStylisticAltThirteenOffSelector }, michael@0: { 'ss14', kStylisticAlternativesType, kStylisticAltFourteenOnSelector, kStylisticAltFourteenOffSelector }, michael@0: { 'ss15', kStylisticAlternativesType, kStylisticAltFifteenOnSelector, kStylisticAltFifteenOffSelector }, michael@0: { 'ss16', kStylisticAlternativesType, kStylisticAltSixteenOnSelector, kStylisticAltSixteenOffSelector }, michael@0: { 'ss17', kStylisticAlternativesType, kStylisticAltSeventeenOnSelector, kStylisticAltSeventeenOffSelector }, michael@0: { 'ss18', kStylisticAlternativesType, kStylisticAltEighteenOnSelector, kStylisticAltEighteenOffSelector }, michael@0: { 'ss19', kStylisticAlternativesType, kStylisticAltNineteenOnSelector, kStylisticAltNineteenOffSelector }, michael@0: { 'ss20', kStylisticAlternativesType, kStylisticAltTwentyOnSelector, kStylisticAltTwentyOffSelector }, michael@0: { 'subs', kVerticalPositionType, kInferiorsSelector, kNormalPositionSelector }, michael@0: { 'sups', kVerticalPositionType, kSuperiorsSelector, kNormalPositionSelector }, michael@0: { 'swsh', kContextualAlternatesType, kSwashAlternatesOnSelector, kSwashAlternatesOffSelector }, michael@0: { 'titl', kStyleOptionsType, kTitlingCapsSelector, kNoStyleOptionsSelector }, michael@0: { 'tnam', kCharacterShapeType, kTraditionalNamesCharactersSelector, 16 }, michael@0: { 'tnum', kNumberSpacingType, kMonospacedNumbersSelector, 4 }, michael@0: { 'trad', kCharacterShapeType, kTraditionalCharactersSelector, 16 }, michael@0: { 'twid', kTextSpacingType, kThirdWidthTextSelector, 7 }, michael@0: { 'unic', kLetterCaseType, 14, 15 }, michael@0: { 'valt', kTextSpacingType, kAltProportionalTextSelector, 7 }, michael@0: { 'vert', kVerticalSubstitutionType, kSubstituteVerticalFormsOnSelector, kSubstituteVerticalFormsOffSelector }, michael@0: { 'vhal', kTextSpacingType, kAltHalfWidthTextSelector, 7 }, michael@0: { 'vkna', kAlternateKanaType, kAlternateVertKanaOnSelector, kAlternateVertKanaOffSelector }, michael@0: { 'vpal', kTextSpacingType, kAltProportionalTextSelector, 7 }, michael@0: { 'vrt2', kVerticalSubstitutionType, kSubstituteVerticalFormsOnSelector, kSubstituteVerticalFormsOffSelector }, michael@0: { 'zero', kTypographicExtrasType, kSlashedZeroOnSelector, kSlashedZeroOffSelector }, michael@0: }; michael@0: michael@0: static int michael@0: _hb_feature_mapping_cmp (const void *key_, const void *entry_) michael@0: { michael@0: unsigned int key = * (unsigned int *) key_; michael@0: const feature_mapping_t * entry = (const feature_mapping_t *) entry_; michael@0: return key < entry->otFeatureTag ? -1 : michael@0: key > entry->otFeatureTag ? 1 : michael@0: 0; michael@0: } michael@0: michael@0: hb_bool_t michael@0: _hb_coretext_shape (hb_shape_plan_t *shape_plan, michael@0: hb_font_t *font, michael@0: hb_buffer_t *buffer, michael@0: const hb_feature_t *features, michael@0: unsigned int num_features) michael@0: { michael@0: hb_face_t *face = font->face; michael@0: hb_coretext_shaper_face_data_t *face_data = HB_SHAPER_DATA_GET (face); michael@0: hb_coretext_shaper_font_data_t *font_data = HB_SHAPER_DATA_GET (font); michael@0: michael@0: /* michael@0: * Set up features. michael@0: * (copied + modified from code from hb-uniscribe.cc) michael@0: */ michael@0: hb_auto_array_t feature_records; michael@0: hb_auto_array_t range_records; michael@0: if (num_features) michael@0: { michael@0: /* Sort features by start/end events. */ michael@0: hb_auto_array_t feature_events; michael@0: for (unsigned int i = 0; i < num_features; i++) michael@0: { michael@0: const feature_mapping_t * mapping = (const feature_mapping_t *) bsearch (&features[i].tag, michael@0: feature_mappings, michael@0: ARRAY_LENGTH (feature_mappings), michael@0: sizeof (feature_mappings[0]), michael@0: _hb_feature_mapping_cmp); michael@0: if (!mapping) michael@0: continue; michael@0: michael@0: active_feature_t feature; michael@0: feature.rec.feature = mapping->aatFeatureType; michael@0: feature.rec.setting = features[i].value ? mapping->selectorToEnable : mapping->selectorToDisable; michael@0: feature.order = i; michael@0: michael@0: feature_event_t *event; michael@0: michael@0: event = feature_events.push (); michael@0: if (unlikely (!event)) michael@0: goto fail_features; michael@0: event->index = features[i].start; michael@0: event->start = true; michael@0: event->feature = feature; michael@0: michael@0: event = feature_events.push (); michael@0: if (unlikely (!event)) michael@0: goto fail_features; michael@0: event->index = features[i].end; michael@0: event->start = false; michael@0: event->feature = feature; michael@0: } michael@0: feature_events.sort (); michael@0: /* Add a strategic final event. */ michael@0: { michael@0: active_feature_t feature; michael@0: feature.rec.feature = HB_TAG_NONE; michael@0: feature.rec.setting = 0; michael@0: feature.order = num_features + 1; michael@0: michael@0: feature_event_t *event = feature_events.push (); michael@0: if (unlikely (!event)) michael@0: goto fail_features; michael@0: event->index = 0; /* This value does magic. */ michael@0: event->start = false; michael@0: event->feature = feature; michael@0: } michael@0: michael@0: /* Scan events and save features for each range. */ michael@0: hb_auto_array_t active_features; michael@0: unsigned int last_index = 0; michael@0: for (unsigned int i = 0; i < feature_events.len; i++) michael@0: { michael@0: feature_event_t *event = &feature_events[i]; michael@0: michael@0: if (event->index != last_index) michael@0: { michael@0: /* Save a snapshot of active features and the range. */ michael@0: range_record_t *range = range_records.push (); michael@0: if (unlikely (!range)) michael@0: goto fail_features; michael@0: michael@0: unsigned int offset = feature_records.len; michael@0: michael@0: if (active_features.len) michael@0: { michael@0: CFMutableArrayRef features_array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); michael@0: michael@0: /* TODO sort and resolve conflicting features? */ michael@0: /* active_features.sort (); */ michael@0: for (unsigned int j = 0; j < active_features.len; j++) michael@0: { michael@0: CFStringRef keys[2] = { michael@0: kCTFontFeatureTypeIdentifierKey, michael@0: kCTFontFeatureSelectorIdentifierKey michael@0: }; michael@0: CFNumberRef values[2] = { michael@0: CFNumberCreate (kCFAllocatorDefault, kCFNumberIntType, &active_features[j].rec.feature), michael@0: CFNumberCreate (kCFAllocatorDefault, kCFNumberIntType, &active_features[j].rec.setting) michael@0: }; michael@0: CFDictionaryRef dict = CFDictionaryCreate (kCFAllocatorDefault, michael@0: (const void **) keys, michael@0: (const void **) values, michael@0: 2, michael@0: &kCFTypeDictionaryKeyCallBacks, michael@0: &kCFTypeDictionaryValueCallBacks); michael@0: CFRelease (values[0]); michael@0: CFRelease (values[1]); michael@0: michael@0: CFArrayAppendValue (features_array, dict); michael@0: CFRelease (dict); michael@0: michael@0: } michael@0: michael@0: CFDictionaryRef attributes = CFDictionaryCreate (kCFAllocatorDefault, michael@0: (const void **) &kCTFontFeatureSettingsAttribute, michael@0: (const void **) &features_array, michael@0: 1, michael@0: &kCFTypeDictionaryKeyCallBacks, michael@0: &kCFTypeDictionaryValueCallBacks); michael@0: CFRelease (features_array); michael@0: michael@0: CTFontDescriptorRef font_desc = CTFontDescriptorCreateWithAttributes (attributes); michael@0: CFRelease (attributes); michael@0: michael@0: range->font = CTFontCreateCopyWithAttributes (font_data->ct_font, 0.0, NULL, font_desc); michael@0: michael@0: CFRelease (font_desc); michael@0: } michael@0: else michael@0: { michael@0: range->font = NULL; michael@0: } michael@0: michael@0: range->index_first = last_index; michael@0: range->index_last = event->index - 1; michael@0: michael@0: last_index = event->index; michael@0: } michael@0: michael@0: if (event->start) { michael@0: active_feature_t *feature = active_features.push (); michael@0: if (unlikely (!feature)) michael@0: goto fail_features; michael@0: *feature = event->feature; michael@0: } else { michael@0: active_feature_t *feature = active_features.find (&event->feature); michael@0: if (feature) michael@0: active_features.remove (feature - active_features.array); michael@0: } michael@0: } michael@0: michael@0: if (!range_records.len) /* No active feature found. */ michael@0: goto fail_features; michael@0: } michael@0: else michael@0: { michael@0: fail_features: michael@0: num_features = 0; michael@0: } michael@0: michael@0: #define FAIL(...) \ michael@0: HB_STMT_START { \ michael@0: DEBUG_MSG (CORETEXT, NULL, __VA_ARGS__); \ michael@0: return false; \ michael@0: } HB_STMT_END; michael@0: michael@0: unsigned int scratch_size; michael@0: hb_buffer_t::scratch_buffer_t *scratch = buffer->get_scratch_buffer (&scratch_size); michael@0: michael@0: #define ALLOCATE_ARRAY(Type, name, len) \ michael@0: Type *name = (Type *) scratch; \ michael@0: { \ michael@0: unsigned int _consumed = DIV_CEIL ((len) * sizeof (Type), sizeof (*scratch)); \ michael@0: assert (_consumed <= scratch_size); \ michael@0: scratch += _consumed; \ michael@0: scratch_size -= _consumed; \ michael@0: } michael@0: michael@0: #define utf16_index() var1.u32 michael@0: michael@0: ALLOCATE_ARRAY (UniChar, pchars, buffer->len * 2); michael@0: michael@0: unsigned int chars_len = 0; michael@0: for (unsigned int i = 0; i < buffer->len; i++) { michael@0: hb_codepoint_t c = buffer->info[i].codepoint; michael@0: buffer->info[i].utf16_index() = chars_len; michael@0: if (likely (c < 0x10000)) michael@0: pchars[chars_len++] = c; michael@0: else if (unlikely (c >= 0x110000)) michael@0: pchars[chars_len++] = 0xFFFD; michael@0: else { michael@0: pchars[chars_len++] = 0xD800 + ((c - 0x10000) >> 10); michael@0: pchars[chars_len++] = 0xDC00 + ((c - 0x10000) & ((1 << 10) - 1)); michael@0: } michael@0: } michael@0: michael@0: #undef utf16_index michael@0: michael@0: CFStringRef string_ref = CFStringCreateWithCharactersNoCopy (NULL, michael@0: pchars, chars_len, michael@0: kCFAllocatorNull); michael@0: michael@0: CFMutableAttributedStringRef attr_string = CFAttributedStringCreateMutable (NULL, chars_len); michael@0: CFAttributedStringReplaceString (attr_string, CFRangeMake (0, 0), string_ref); michael@0: CFAttributedStringSetAttribute (attr_string, CFRangeMake (0, chars_len), michael@0: kCTFontAttributeName, font_data->ct_font); michael@0: michael@0: if (num_features) michael@0: { michael@0: ALLOCATE_ARRAY (unsigned int, log_clusters, chars_len); michael@0: michael@0: /* Need log_clusters to assign features. */ michael@0: chars_len = 0; michael@0: for (unsigned int i = 0; i < buffer->len; i++) michael@0: { michael@0: hb_codepoint_t c = buffer->info[i].codepoint; michael@0: unsigned int cluster = buffer->info[i].cluster; michael@0: log_clusters[chars_len++] = cluster; michael@0: if (c >= 0x10000 && c < 0x110000) michael@0: log_clusters[chars_len++] = cluster; /* Surrogates. */ michael@0: } michael@0: michael@0: unsigned int start = 0; michael@0: range_record_t *last_range = &range_records[0]; michael@0: for (unsigned int k = 0; k < chars_len; k++) michael@0: { michael@0: range_record_t *range = last_range; michael@0: while (log_clusters[k] < range->index_first) michael@0: range--; michael@0: while (log_clusters[k] > range->index_last) michael@0: range++; michael@0: if (range != last_range) michael@0: { michael@0: if (last_range->font) michael@0: CFAttributedStringSetAttribute (attr_string, CFRangeMake (start, k - start), michael@0: kCTFontAttributeName, last_range->font); michael@0: michael@0: start = k; michael@0: } michael@0: michael@0: last_range = range; michael@0: } michael@0: if (start != chars_len && last_range->font) michael@0: CFAttributedStringSetAttribute (attr_string, CFRangeMake (start, chars_len - start - 1), michael@0: kCTFontAttributeName, last_range->font); michael@0: michael@0: for (unsigned int i = 0; i < range_records.len; i++) michael@0: if (range_records[i].font) michael@0: CFRelease (range_records[i].font); michael@0: } michael@0: michael@0: CTLineRef line = CTLineCreateWithAttributedString (attr_string); michael@0: CFRelease (attr_string); michael@0: michael@0: CFArrayRef glyph_runs = CTLineGetGlyphRuns (line); michael@0: unsigned int num_runs = CFArrayGetCount (glyph_runs); michael@0: michael@0: buffer->len = 0; michael@0: michael@0: const CFRange range_all = CFRangeMake (0, 0); michael@0: michael@0: for (unsigned int i = 0; i < num_runs; i++) michael@0: { michael@0: CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (glyph_runs, i); michael@0: michael@0: /* CoreText does automatic font fallback (AKA "cascading") for characters michael@0: * not supported by the requested font, and provides no way to turn it off, michael@0: * so we detect if the returned run uses a font other than the requested michael@0: * one and fill in the buffer with .notdef glyphs instead of random glyph michael@0: * indices from a different font. michael@0: */ michael@0: CFDictionaryRef attributes = CTRunGetAttributes (run); michael@0: CTFontRef run_ct_font = static_cast(CFDictionaryGetValue (attributes, kCTFontAttributeName)); michael@0: CGFontRef run_cg_font = CTFontCopyGraphicsFont (run_ct_font, 0); michael@0: if (!CFEqual (run_cg_font, face_data->cg_font)) michael@0: { michael@0: CFRelease (run_cg_font); michael@0: michael@0: CFRange range = CTRunGetStringRange (run); michael@0: buffer->ensure (buffer->len + range.length); michael@0: if (buffer->in_error) michael@0: FAIL ("Buffer resize failed"); michael@0: hb_glyph_info_t *info = buffer->info + buffer->len; michael@0: michael@0: CGGlyph notdef = 0; michael@0: double advance = CTFontGetAdvancesForGlyphs (font_data->ct_font, kCTFontHorizontalOrientation, ¬def, NULL, 1); michael@0: michael@0: for (CFIndex j = range.location; j < range.location + range.length; j++) michael@0: { michael@0: UniChar ch = CFStringGetCharacterAtIndex (string_ref, j); michael@0: if (hb_in_range (ch, 0xDC00, 0xDFFF) && range.location < j) michael@0: { michael@0: ch = CFStringGetCharacterAtIndex (string_ref, j - 1); michael@0: if (hb_in_range (ch, 0xD800, 0xDBFF)) michael@0: /* This is the second of a surrogate pair. Don't need .notdef michael@0: * for this one. */ michael@0: continue; michael@0: } michael@0: michael@0: info->codepoint = notdef; michael@0: /* TODO We have to fixup clusters later. See vis_clusters in michael@0: * hb-uniscribe.cc for example. */ michael@0: info->cluster = j; michael@0: michael@0: info->mask = advance; michael@0: info->var1.u32 = 0; michael@0: info->var2.u32 = 0; michael@0: michael@0: info++; michael@0: buffer->len++; michael@0: } michael@0: continue; michael@0: } michael@0: CFRelease (run_cg_font); michael@0: michael@0: unsigned int num_glyphs = CTRunGetGlyphCount (run); michael@0: if (num_glyphs == 0) michael@0: continue; michael@0: michael@0: buffer->ensure (buffer->len + num_glyphs); michael@0: michael@0: scratch = buffer->get_scratch_buffer (&scratch_size); michael@0: michael@0: /* Testing indicates that CTRunGetGlyphsPtr, etc (almost?) always michael@0: * succeed, and so copying data to our own buffer will be rare. */ michael@0: michael@0: const CGGlyph* glyphs = CTRunGetGlyphsPtr (run); michael@0: if (!glyphs) { michael@0: ALLOCATE_ARRAY (CGGlyph, glyph_buf, num_glyphs); michael@0: CTRunGetGlyphs (run, range_all, glyph_buf); michael@0: glyphs = glyph_buf; michael@0: } michael@0: michael@0: const CGPoint* positions = CTRunGetPositionsPtr (run); michael@0: if (!positions) { michael@0: ALLOCATE_ARRAY (CGPoint, position_buf, num_glyphs); michael@0: CTRunGetPositions (run, range_all, position_buf); michael@0: positions = position_buf; michael@0: } michael@0: michael@0: const CFIndex* string_indices = CTRunGetStringIndicesPtr (run); michael@0: if (!string_indices) { michael@0: ALLOCATE_ARRAY (CFIndex, index_buf, num_glyphs); michael@0: CTRunGetStringIndices (run, range_all, index_buf); michael@0: string_indices = index_buf; michael@0: } michael@0: michael@0: #undef ALLOCATE_ARRAY michael@0: michael@0: double run_width = CTRunGetTypographicBounds (run, range_all, NULL, NULL, NULL); michael@0: michael@0: for (unsigned int j = 0; j < num_glyphs; j++) { michael@0: double advance = (j + 1 < num_glyphs ? positions[j + 1].x : positions[0].x + run_width) - positions[j].x; michael@0: michael@0: hb_glyph_info_t *info = &buffer->info[buffer->len]; michael@0: michael@0: info->codepoint = glyphs[j]; michael@0: info->cluster = string_indices[j]; michael@0: michael@0: /* Currently, we do all x-positioning by setting the advance, we never use x-offset. */ michael@0: info->mask = advance; michael@0: info->var1.u32 = 0; michael@0: info->var2.u32 = positions[j].y; michael@0: michael@0: buffer->len++; michael@0: } michael@0: } michael@0: michael@0: buffer->clear_positions (); michael@0: michael@0: unsigned int count = buffer->len; michael@0: for (unsigned int i = 0; i < count; ++i) { michael@0: hb_glyph_info_t *info = &buffer->info[i]; michael@0: hb_glyph_position_t *pos = &buffer->pos[i]; michael@0: michael@0: /* TODO vertical */ michael@0: pos->x_advance = info->mask; michael@0: pos->x_offset = info->var1.u32; michael@0: pos->y_offset = info->var2.u32; michael@0: } michael@0: michael@0: /* Fix up clusters so that we never return out-of-order indices; michael@0: * if core text has reordered glyphs, we'll merge them to the michael@0: * beginning of the reordered cluster. michael@0: * michael@0: * This does *not* mean we'll form the same clusters as Uniscribe michael@0: * or the native OT backend, only that the cluster indices will be michael@0: * monotonic in the output buffer. */ michael@0: if (HB_DIRECTION_IS_FORWARD (buffer->props.direction)) { michael@0: unsigned int prev_cluster = 0; michael@0: for (unsigned int i = 0; i < count; i++) { michael@0: unsigned int curr_cluster = buffer->info[i].cluster; michael@0: if (curr_cluster < prev_cluster) { michael@0: for (unsigned int j = i; j > 0; j--) { michael@0: if (buffer->info[j - 1].cluster > curr_cluster) michael@0: buffer->info[j - 1].cluster = curr_cluster; michael@0: else michael@0: break; michael@0: } michael@0: } michael@0: prev_cluster = curr_cluster; michael@0: } michael@0: } else { michael@0: unsigned int prev_cluster = (unsigned int)-1; michael@0: for (unsigned int i = 0; i < count; i++) { michael@0: unsigned int curr_cluster = buffer->info[i].cluster; michael@0: if (curr_cluster > prev_cluster) { michael@0: for (unsigned int j = i; j > 0; j--) { michael@0: if (buffer->info[j - 1].cluster < curr_cluster) michael@0: buffer->info[j - 1].cluster = curr_cluster; michael@0: else michael@0: break; michael@0: } michael@0: } michael@0: prev_cluster = curr_cluster; michael@0: } michael@0: } michael@0: michael@0: CFRelease (string_ref); michael@0: CFRelease (line); michael@0: michael@0: return true; michael@0: }