Merge pull request #122 from Shirk/ligature_fixes

Fix spacing issues on ligatures as seen on #36.
This commit is contained in:
Kazuki Sakamoto
2015-11-13 12:12:15 -08:00
3 changed files with 113 additions and 52 deletions
+15 -15
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="8152.3" systemVersion="15A216g" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9059" systemVersion="15C31f" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<dependencies>
<deployment version="1050" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="8152.3"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9059"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MMPreferenceController">
@@ -192,11 +192,11 @@
<animations/>
</customView>
<customView id="620" userLabel="Advanced">
<rect key="frame" x="0.0" y="0.0" width="483" height="381"/>
<rect key="frame" x="0.0" y="0.0" width="483" height="407"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField verticalHuggingPriority="750" id="826">
<rect key="frame" x="17" y="170" width="449" height="56"/>
<rect key="frame" x="17" y="173" width="449" height="56"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
<textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" id="993">
@@ -207,7 +207,7 @@
</textFieldCell>
</textField>
<button id="817">
<rect key="frame" x="18" y="228" width="133" height="18"/>
<rect key="frame" x="18" y="231" width="133" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
<buttonCell key="cell" type="check" title="Enable Quickstart" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="992">
@@ -220,7 +220,7 @@
</connections>
</button>
<textField verticalHuggingPriority="750" id="815">
<rect key="frame" x="17" y="315" width="449" height="28"/>
<rect key="frame" x="17" y="341" width="449" height="28"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
<textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" id="991">
@@ -231,18 +231,18 @@
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" id="UsV-sz-DPX">
<rect key="frame" x="38" y="257" width="423" height="28"/>
<rect key="frame" x="38" y="255" width="423" height="56"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
<textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" id="v4V-eb-KHB">
<font key="font" metaFont="smallSystem"/>
<string key="title">Selecting this option will enable full support for displaying font ligatures. Using this with a non-proportional font may result in layout issues.</string>
<string key="title">Selecting this option will enable full support for displaying font ligatures. Using this with a non-proportional font may result in layout issues. Please note that liguture support currenty requires 'set cursorline' or 'set relativenumber' to draw properly.</string>
<color key="textColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button id="782">
<rect key="frame" x="18" y="345" width="174" height="18"/>
<rect key="frame" x="18" y="371" width="174" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
<buttonCell key="cell" type="check" title="Use Core Text renderer" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="990">
@@ -255,7 +255,7 @@
</connections>
</button>
<button id="Y9N-gx-Zgy">
<rect key="frame" x="38" y="291" width="191" height="18"/>
<rect key="frame" x="38" y="317" width="191" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
<buttonCell key="cell" type="check" title="Enable support for ligatures" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="fZO-bR-PtE">
@@ -268,7 +268,7 @@
</connections>
</button>
<textField verticalHuggingPriority="750" id="1001">
<rect key="frame" x="17" y="74" width="444" height="70"/>
<rect key="frame" x="17" y="77" width="444" height="70"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
<textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" id="1004">
@@ -279,7 +279,7 @@
</textFieldCell>
</textField>
<button id="1013">
<rect key="frame" x="18" y="146" width="174" height="18"/>
<rect key="frame" x="18" y="149" width="174" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
<buttonCell key="cell" type="check" title="Draw marked text inline" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="1014">
@@ -291,7 +291,7 @@
</connections>
</button>
<textField verticalHuggingPriority="750" id="1017">
<rect key="frame" x="17" y="20" width="415" height="28"/>
<rect key="frame" x="17" y="23" width="415" height="28"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
<textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" id="1020">
@@ -302,7 +302,7 @@
</textFieldCell>
</textField>
<button id="1028">
<rect key="frame" x="18" y="50" width="388" height="18"/>
<rect key="frame" x="18" y="53" width="388" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
<buttonCell key="cell" type="check" title="Prefer native full-screen support (requires Mac OS X 10.7)" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="1029">
@@ -315,7 +315,7 @@
</button>
</subviews>
<animations/>
<point key="canvasLocation" x="592.5" y="797.5"/>
<point key="canvasLocation" x="592.5" y="810.5"/>
</customView>
</objects>
</document>
Binary file not shown.
+98 -37
View File
@@ -1039,6 +1039,53 @@ lookupFont(NSMutableArray *fontCache, const unichar *chars, UniCharCount count,
return newFontRef;
}
static CFAttributedStringRef
attributedStringForString(NSString *string, const CTFontRef font, BOOL useLigatures)
{
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:
(id)font, kCTFontAttributeName,
// 2 - full ligatures including rare
// 1 - basic ligatures
// 0 - no ligatures
[NSNumber numberWithInteger: (useLigatures) ? 1 : 0], kCTLigatureAttributeName,
nil
];
return CFAttributedStringCreate(NULL, (CFStringRef)string, (CFDictionaryRef)attrs);
}
static UniCharCount
fetchGlyphsAndAdvances(const CTLineRef line, CGGlyph *glyphs, CGSize *advances, UniCharCount length)
{
NSArray *glyphRuns = (NSArray*)CTLineGetGlyphRuns(line);
// get a hold on the actual character widths and glyphs in line
UniCharCount offset = 0;
for (id item in glyphRuns) {
CTRunRef run = (CTRunRef)item;
CFIndex count = CTRunGetGlyphCount(run);
if(count > 0 && count - offset > length) {
count = length - offset;
}
CFRange range = CFRangeMake(0, count);
if( glyphs != NULL ) {
CTRunGetGlyphs(run, range, &glyphs[offset]);
}
if( advances != NULL ) {
CTRunGetAdvances(run, range, &advances[offset]);
}
offset += count;
if(offset >= length) {
break;
}
}
return offset;
}
static UniCharCount
gatherGlyphs(CGGlyph glyphs[], UniCharCount count)
{
@@ -1062,52 +1109,65 @@ ligatureGlyphsForChars(const unichar *chars, CGGlyph *glyphs, CGPoint *positions
* The way proposed on the CoreText ML is to convert the text to an attributed
* string, create a CTLine from it and retrieve the Glyphs from the CTRuns in it.
*/
NSString *text = [NSString stringWithCharacters:chars
length:*length];
CGGlyph refGlyphs[*length];
CGPoint refPositions[*length];
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:
(id)font, kCTFontAttributeName,
// 2 - full ligatures including rare
[NSNumber numberWithInteger: 2], kCTLigatureAttributeName,
nil
];
memcpy(refGlyphs, glyphs, sizeof(CGGlyph) * (*length));
memcpy(refPositions, positions, sizeof(CGSize) * (*length));
NSAttributedString *attrText = [[NSAttributedString alloc] initWithString:text
attributes:attrs];
memset(glyphs, 0, sizeof(CGGlyph) * (*length));
CGPoint refPos = positions[0];
NSString *plainText = [NSString stringWithCharacters:chars length:*length];
CFAttributedStringRef ligatureText = attributedStringForString(plainText, font, YES);
CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)attrText);
CTLineRef ligature = CTLineCreateWithAttributedString(ligatureText);
UniCharCount offset = 0;
NSArray *glyphRuns = (NSArray*)CTLineGetGlyphRuns(line);
CGSize ligatureRanges[*length], regularRanges[*length];
for (id item in glyphRuns) {
CTRunRef run = (CTRunRef)item;
CFIndex count = CTRunGetGlyphCount(run);
// get the (ligature)glyphs and advances for the new text
UniCharCount offset = fetchGlyphsAndAdvances(ligature, glyphs, ligatureRanges, length);
// fetch the advances for the base text
CTFontGetAdvancesForGlyphs(font, kCTFontOrientationDefault, refGlyphs, regularRanges, *length);
if(count > 0) {
if(count - offset > *length) {
count = (*length) - offset;
}
CFRelease(ligatureText);
CFRelease(ligature);
// tricky part: compare both advance ranges and chomp positions which
// are covered by a single ligature while keeping glyphs not in the ligature font.
#define fequal(a, b) (fabs( (a) - (b) ) < FLT_EPSILON)
#define fless(a, b)((a) - (b) < FLT_EPSILON) && (fabs( (a) - (b) ) > FLT_EPSILON)
CFIndex skip = 0;
for( CFIndex i = 0; i < offset && skip + i < *length; ++i ) {
memcpy(&positions[i], &refPositions[skip + i], sizeof(CGSize));
if( fequal(ligatureRanges[i].width, regularRanges[skip + i].width) ) {
// [mostly] same width
continue;
} else if( fless(ligatureRanges[i].width, regularRanges[skip + i].width) ) {
// original is wider than our result - use the original glyph
// FIXME: this is currently the only way to detect emoji (except for 'glyph[i] == 5')
glyphs[i] = refGlyphs[skip + i];
continue;
}
CFRange range = CFRangeMake(0, count);
CTRunGetGlyphs(run, range, &glyphs[offset]);
CTRunGetPositions(run, range, &positions[offset]);
// no, that's a ligature
// count how many positions this glyph would take up in the base text
CFIndex j = 0;
float width = ceil(regularRanges[skip + i].width);
offset += count;
if(offset >= *length) {
// don't copy more glyphs then there is space for
break;
while( (int)width < (int)ligatureRanges[i].width
&& skip + i + j < *length )
{
width += ceil(regularRanges[++j + skip + i].width);
}
skip += j;
}
// fixup relative positioning
CFIndex i;
for( i = 0; i < offset; ++i ) {
positions[i].x += refPos.x;
positions[i].y += refPos.y;
}
#undef fless
#undef fequal
// as ligatures combine characters it is required to adjust the
// original length value
*length = offset;
@@ -1120,10 +1180,11 @@ recurseDraw(const unichar *chars, CGGlyph *glyphs, CGPoint *positions,
{
if (CTFontGetGlyphsForCharacters(fontRef, chars, glyphs, length)) {
// All chars were mapped to glyphs, so draw all at once and return.
length = gatherGlyphs(glyphs, length);
if (useLigatures) {
memset(glyphs, 0, sizeof(CGGlyph) * length);
ligatureGlyphsForChars(chars, glyphs, positions, &length, fontRef);
ligatureGlyphsForChars(chars, glyphs, positions, &length, fontRef);
} else {
// only fixup surrogate pairs if we're not using ligatures
length = gatherGlyphs(glyphs, length);
}
CTFontDrawGlyphs(fontRef, glyphs, positions, length, context);