From cb29cbf4a5d3cdd6dcab75eacfdf5718d1858625 Mon Sep 17 00:00:00 2001 From: jules Date: Wed, 30 Oct 2013 10:43:38 +0000 Subject: [PATCH] Fix for vertical justification of CoreText layouts of attributed strings. --- .../juce_graphics/native/juce_mac_Fonts.mm | 117 +++++++++++++----- 1 file changed, 83 insertions(+), 34 deletions(-) diff --git a/modules/juce_graphics/native/juce_mac_Fonts.mm b/modules/juce_graphics/native/juce_mac_Fonts.mm index aa1851a131..2c38bddb24 100644 --- a/modules/juce_graphics/native/juce_mac_Fonts.mm +++ b/modules/juce_graphics/native/juce_mac_Fonts.mm @@ -189,6 +189,18 @@ namespace CoreTextTypeLayout HeapBlock local; }; + struct LineInfo + { + LineInfo (CTFrameRef frame, CTLineRef line, CFIndex lineIndex) + { + CTFrameGetLineOrigins (frame, CFRangeMake (lineIndex, 1), &origin); + CTLineGetTypographicBounds (line, &ascent, &descent, &leading); + } + + CGPoint origin; + CGFloat ascent, descent, leading; + }; + static CTFontRef getOrCreateFont (const Font& f) { if (CTFontRef ctf = getCTFontFromTypeface (f)) @@ -217,15 +229,15 @@ namespace CoreTextTypeLayout for (int i = 0; i < numCharacterAttributes; ++i) { - const AttributedString::Attribute* const attr = text.getAttribute (i); + const AttributedString::Attribute& attr = *text.getAttribute (i); - if (attr->range.getStart() > CFAttributedStringGetLength (attribString)) + if (attr.range.getStart() > CFAttributedStringGetLength (attribString)) continue; - Range range (attr->range); + Range range (attr.range); range.setEnd (jmin (range.getEnd(), (int) CFAttributedStringGetLength (attribString))); - if (const Font* const f = attr->getFont()) + if (const Font* const f = attr.getFont()) { if (CTFontRef ctFontRef = getOrCreateFont (*f)) { @@ -237,7 +249,7 @@ namespace CoreTextTypeLayout } } - if (const Colour* const col = attr->getColour()) + if (const Colour* const col = attr.getColour()) { #if JUCE_IOS const CGFloat components[] = { col->getFloatRed(), @@ -303,39 +315,78 @@ namespace CoreTextTypeLayout return attribString; } - static void drawToCGContext (const AttributedString& text, const Rectangle& area, - const CGContextRef& context, const float flipHeight) + static CTFrameRef createCTFrame (const AttributedString& text, CGRect bounds) { - CFAttributedStringRef attribString = CoreTextTypeLayout::createCFAttributedString (text); + CFAttributedStringRef attribString = createCFAttributedString (text); CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString (attribString); CFRelease (attribString); CGMutablePathRef path = CGPathCreateMutable(); - CGRect bounds = CGRectMake ((CGFloat) area.getX(), flipHeight - (CGFloat) area.getBottom(), - (CGFloat) area.getWidth(), (CGFloat) area.getHeight()); CGPathAddRect (path, nullptr, bounds); CTFrameRef frame = CTFramesetterCreateFrame (framesetter, CFRangeMake (0, 0), path, nullptr); CFRelease (framesetter); CGPathRelease (path); - CTFrameDraw (frame, context); + return frame; + } + + static Range getLineVerticalRange (CTFrameRef frame, CFArrayRef lines, int lineIndex) + { + LineInfo info (frame, (CTLineRef) CFArrayGetValueAtIndex (lines, lineIndex), lineIndex); + return Range ((float) (info.origin.y - info.descent), + (float) (info.origin.y + info.ascent)); + } + + static float findCTFrameHeight (CTFrameRef frame) + { + CFArrayRef lines = CTFrameGetLines (frame); + const CFIndex numLines = CFArrayGetCount (lines); + + if (numLines == 0) + return 0; + + Range range (getLineVerticalRange (frame, lines, 0)); + + if (numLines > 1) + range = range.getUnionWith (getLineVerticalRange (frame, lines, numLines - 1)); + + return range.getLength(); + } + + static void drawToCGContext (const AttributedString& text, const Rectangle& area, + const CGContextRef& context, const float flipHeight) + { + CTFrameRef frame = createCTFrame (text, CGRectMake ((CGFloat) area.getX(), flipHeight - (CGFloat) area.getBottom(), + (CGFloat) area.getWidth(), (CGFloat) area.getHeight())); + + const int verticalJustification = text.getJustification().getOnlyVerticalFlags(); + + if (verticalJustification == Justification::verticallyCentred + || verticalJustification == Justification::bottom) + { + float adjust = area.getHeight() - findCTFrameHeight (frame); + + if (verticalJustification == Justification::verticallyCentred) + adjust *= 0.5f; + + CGContextSaveGState (context); + CGContextTranslateCTM (context, 0, -adjust); + CTFrameDraw (frame, context); + CGContextRestoreGState (context); + } + else + { + CTFrameDraw (frame, context); + } + CFRelease (frame); } static void createLayout (TextLayout& glyphLayout, const AttributedString& text) { - CFAttributedStringRef attribString = CoreTextTypeLayout::createCFAttributedString (text); - CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString (attribString); - CFRelease (attribString); - - CGMutablePathRef path = CGPathCreateMutable(); - const CGRect bounds = CGRectMake (0, 0, glyphLayout.getWidth(), 1.0e6f); - CGPathAddRect (path, nullptr, bounds); - - CTFrameRef frame = CTFramesetterCreateFrame (framesetter, CFRangeMake(0, 0), path, nullptr); - CFRelease (framesetter); - CGPathRelease (path); + const CGFloat boundsHeight = 1.0e6f; + CTFrameRef frame = createCTFrame (text, CGRectMake (0, 0, glyphLayout.getWidth(), boundsHeight)); CFArrayRef lines = CTFrameGetLines (frame); const CFIndex numLines = CFArrayGetCount (lines); @@ -353,16 +404,14 @@ namespace CoreTextTypeLayout const CFIndex lineStringEnd = cfrlineStringRange.location + cfrlineStringRange.length - 1; const Range lineStringRange ((int) cfrlineStringRange.location, (int) lineStringEnd); - CGPoint cgpLineOrigin; - CTFrameGetLineOrigins (frame, CFRangeMake(i, 1), &cgpLineOrigin); + LineInfo lineInfo (frame, line, i); - Point lineOrigin ((float) cgpLineOrigin.x, bounds.size.height - (float) cgpLineOrigin.y); - - CGFloat ascent, descent, leading; - CTLineGetTypographicBounds (line, &ascent, &descent, &leading); - - TextLayout::Line* const glyphLine = new TextLayout::Line (lineStringRange, lineOrigin, - (float) ascent, (float) descent, (float) leading, + TextLayout::Line* const glyphLine = new TextLayout::Line (lineStringRange, + Point ((float) lineInfo.origin.x, + boundsHeight - (float) lineInfo.origin.y), + (float) lineInfo.ascent, + (float) lineInfo.descent, + (float) lineInfo.leading, (int) numRuns); glyphLayout.addLine (glyphLine); @@ -409,9 +458,9 @@ namespace CoreTextTypeLayout glyphRun->colour = Colour::fromFloatRGBA (components[0], components[1], components[2], components[3]); } - const CoreTextTypeLayout::Glyphs glyphs (run, (size_t) numGlyphs); - const CoreTextTypeLayout::Advances advances (run, numGlyphs); - const CoreTextTypeLayout::Positions positions (run, (size_t) numGlyphs); + const Glyphs glyphs (run, (size_t) numGlyphs); + const Advances advances (run, numGlyphs); + const Positions positions (run, (size_t) numGlyphs); for (CFIndex k = 0; k < numGlyphs; ++k) glyphRun->glyphs.add (TextLayout::Glyph (glyphs.glyphs[k], Point (positions.points[k].x,