كيف يمكنني وهمية مرتفع و منخفض مع جوهر النص و يعزى السلسلة ؟

StackOverflow https://stackoverflow.com/questions/3871303

سؤال

انا استخدم NSMutableAttribtuedString من أجل بناء سلسلة مع تنسيق ، ثم تمر إلى جوهر النص أن تجعل في إطار.المشكلة هي أنني بحاجة إلى استخدام مرتفع و منخفض.ما لم تكن هذه الشخصيات متوفرة في الخط (معظم الخطوط لا تدعم ذلك) ، ثم تحديد الملكية kCTSuperscriptAttributeName لا يفعل شيئا على الإطلاق.

لذا أعتقد أنا مع ترك الخيار الوحيد الذي وهمية عن طريق تغيير حجم الخط و تحريك قاعدة خط.أستطيع أن أفعل حجم الخط قليلا, ولكن لا أعرف رمز تغيير خط الأساس.أي شخص يمكن أن يساعد من فضلك ؟

وذلك بفضل!

تحرير:أنا أفكر النظر في مقدار الوقت المتوفر لدي من أجل حل هذه المشكلة ، من تحرير الخط بحيث انها تعطى منخفض "2"...إما هذا أو إيجاد المدمج في باد الخط الذي يفعل.لا أحد يعرف من أي الخط الرقيق مع منخفض "2" يمكنني استخدامها ؟

هل كانت مفيدة؟

المحلول

لا يوجد إعداد أساسي بين CtparagraphstylesPecifiers أو ثوابت اسم سمة السلسلة المحددة. أعتقد أنه من الآمن أن نستنتج أن Coretext لا يدعم بحد ذاته خاصية ضبط الأساس على النص. هناك إشارة إلى وضع الأساس في CTTypesetter ، لكن لا يمكنني ربط ذلك بأي قدرة على تغيير خط الأساس على مدار خط في Coretext's iPad.

وبالتالي ، ربما تحتاج إلى التدخل في عملية التقديم بنفسك. فمثلا:

  • إنشاء ctframesetter ، على سبيل المثال عبر CTFramesetterCreateWithAttributedString
  • احصل على ctframe من ذلك عبر CTFramesetterCreateFrame
  • استعمال CTFrameGetLineOrigins و CTFrameGetLines للحصول على مجموعة من ctlines وحيث يجب رسمها (أي ، يتم تطبيق النص مع فترات الفقرة/الأسطر المناسبة وجميع سمات نص المواقع الرائدة/الرائدة/الأخرى التي يتم تطبيقها)
  • من هؤلاء ، للخطوط التي لا تحتوي على superscript أو subcript ، ما عليك سوى استخدام CTLineDraw وتنسى ذلك
  • لأولئك الذين يعانون من superscript أو subcript ، استخدم CTLineGetGlyphRuns للحصول على مجموعة من كائنات CTRUN التي تصف الحروف الرسومية المختلفة على الخط
  • في كل تشغيل ، استخدم CTRunGetStringIndices لتحديد أحرف المصدر الموجودة في الجري ؛ إذا لم يتم تضمين أي شيء تريده CTRunDraw لرسم الشيء
  • خلاف ذلك ، الاستخدام CTRunGetGlyphs لكسر الجري إلى حروف رسمية فردية و CTRunGetPositions لمعرفة أين سيتم رسمها في الجولة العادية للأشياء
  • استعمال CGContextShowGlyphsAtPoint حسب الاقتضاء ، قم بتعديل مصفوفة النص لأولئك الذين تريدهم في SuperScript أو Subcript

لم أجد بعد طريقة للاستعلام عما إذا كان الخط يحتوي على تلميحات ذات صلة لتوليد SuperScript/Subcript التلقائي ، مما يجعل الأمور صعبة بعض الشيء. إذا كنت يائسًا ولم يكن لديك حل لذلك ، فمن المحتمل أن يكون من الأسهل عدم استخدام أشياء Coretext على الإطلاق - وفي هذه الحالة ، ربما يجب أن تحدد سمة خاصة بك (لهذا السبب [NS/CF] يعزى السمات التعسفية إلى السمات التعسفية يتم تطبيقها ، والتي تم تحديدها باسم السلسلة) واستخدم طرق البحث العادية NSString لتحديد المناطق التي تحتاج إلى طباعتها في SuperScript أو Scarcer من Blind.

لأسباب تتعلق بالأداء ، ربما يكون البحث الثنائي هو الطريق للاستمرار في البحث في جميع الخطوط ، والتشغيل داخل الخط والخداع داخل المدى لمن يهتمون بهم. على افتراض أن لديك فئة فرعية مخصصة UIVIEW لرسم محتوى Coretext ، فمن المحتمل أن يكون ذلك أكثر ذكاءً للقيام بذلك في وقت مبكر بدلاً من كل عملية سحب: (أو الأساليب المكافئة ، إذا كنت تستخدم catiledlayer).

أيضًا ، تحتوي أساليب CTRUN على متغيرات تطلب مؤشرًا إلى مجموعة C التي تحتوي على الأشياء التي تطلبها نسخًا منها ، وربما توفر لك عملية نسخ ولكن ليس بالضرورة تنجح. تحقق من الوثائق. لقد تأكدت للتو من أنني رسمت حلًا عمليًا بدلاً من رسم المسار الأمثل بالضرورة من خلال API Coretext.

نصائح أخرى

فيما يلي بعض التعليمات البرمجية التي تعتمد على مخطط تومي الذي يقوم بالمهمة بشكل جيد (تم اختباره على خطوط واحدة فقط). اضبط خط الأساس على السلسلة المنسوبة مع @"MDBaselineAdjust", ، وهذا الرمز يرسم الخط إلى offset, ، أ CGPoint. للحصول على superscript ، قم أيضًا بتخفيض حجم الخط. معاينة ما هو ممكن: http://cloud.mochidev.com/ifpf (الخط الذي يقرأ [XE] 4F14...")

أتمنى أن يساعدك هذا :)

NSAttributedString *string = ...;
CGPoint origin = ...;

CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)string);
CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, string.length), NULL, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), NULL);
CGPathRef path = CGPathCreateWithRect(CGRectMake(origin.x, origin.y, suggestedSize.width, suggestedSize.height), NULL);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, string.length), path, NULL);
NSArray *lines = (NSArray *)CTFrameGetLines(frame);
if (lines.count) {
    CGPoint *lineOrigins = malloc(lines.count * sizeof(CGPoint));
    CTFrameGetLineOrigins(frame, CFRangeMake(0, lines.count), lineOrigins);

    int i = 0;
    for (id aLine in lines) {
        NSArray *glyphRuns = (NSArray *)CTLineGetGlyphRuns((CTLineRef)aLine);

        CGFloat width = origin.x+lineOrigins[i].x-lineOrigins[0].x;

        for (id run in glyphRuns) {
            CFRange range = CTRunGetStringRange((CTRunRef)run);
            NSDictionary *dict = [string attributesAtIndex:range.location effectiveRange:NULL];
            CGFloat baselineAdjust = [[dict objectForKey:@"MDBaselineAdjust"] doubleValue];

            CGContextSetTextPosition(context, width, origin.y+baselineAdjust);

            CTRunDraw((CTRunRef)run, context, CFRangeMake(0, 0));
        }

        i++;
    }

    free(lineOrigins);
}
CFRelease(frame);
CGPathRelease(path);
CFRelease(framesetter);

`

يمكنك محاكاة الاشتراكات الآن باستخدام TextKit في iOS7. مثال:

NSMutableAttributedString *carbonDioxide = [[NSMutableAttributedString alloc] initWithString:@"CO2"];
[carbonDioxide addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:8] range:NSMakeRange(2, 1)];
[carbonDioxide addAttribute:NSBaselineOffsetAttributeName value:@(-2) range:NSMakeRange(2, 1)];

Image of attributed string output

لقد واجهت مشكلة مع هذا بنفسي. تدعي الوثائق النصية الأساسية لـ Apple أن هناك دعمًا في iOS منذ الإصدار 3.2 ، ولكن لسبب ما لا يزال لا يعمل. حتى في iOS 5 ... كم هو محبط للغاية>.

تمكنت من العثور على حل بديل إذا كنت تهتم حقًا بأرقام SuperScript أو Subcript. لنفترض أن لديك كتلة من النص قد تحتوي على علامة "Sub2" حيث تريد رقمًا ترجميًا 2. استخدم nsgularexpression للعثور على العلامات ، ثم استخدم طريقة replacementStringForresult على كائن regex الخاص بك لاستبدال كل علامة بأحرف Unicode:

if ([match isEqualToString:@"<sub2/>"])
{
   replacement = @"₂";
}

إذا كنت تستخدم عارض حرف OSX ، فيمكنك إسقاط أحرف Unicode مباشرة في الكود الخاص بك. هناك مجموعة من الأحرف الموجودة هناك تسمى "الأرقام" التي تحتوي على جميع أحرف الأرقام المفرطة والانتشار. فقط اترك المؤشر الخاص بك في المكان المناسب في نافذة الكود الخاصة بك وانقر نقرًا مزدوجًا في عارض الأحرف لإدراج الحرف الذي تريده.

مع الخط الصحيح ، ربما يمكنك القيام بذلك مع أي حرف أيضًا ، لكن خريطة الأحرف تحتوي فقط على حفنة من غير الأرقام المتاحة لهذا.

بدلاً من ذلك ، يمكنك فقط وضع أحرف Unicode في محتوى المصدر الخاص بك ، ولكن في كثير من الحالات (مثل لي) ، هذا غير ممكن.

سويفت 4

فضفاضة جدا على الخروج من غراهام الامتيازات الإجابة.أنا لا يمكن أن تجعل له قانون العمل كما هو ولكن بعد ثلاث ساعات من العمل لقد خلق شيء يعمل كبيرة!إذا كنت تفضل تنفيذ هذا جنبا إلى جنب مع مجموعة من أنيق الأداء الأخرى و الميزة إضافات (وصلات ، المتزامن الرسم ، الخ) ، تحقق من بلدي ملف واحد المكتبة DYLabel.إذا لم يكن, قراءة.

أنا أشرح لك كل شيء أقوم به في التعليقات.هذا هو رسم طريقة تسمى من drawRect:

/// Draw text on a given context. Supports superscript using NSBaselineOffsetAttributeName
///
/// This method works by drawing the text backwards (i.e. last line first). This is very very important because it's how we ensure superscripts don't overlap the text above it. In other words, we need to start from the bottom, get the height of the text we just drew, and then draw the next text above it. This could be done in a forward direction but you'd have to use lookahead which IMO is more work.
///
/// If you have to modify on this, remember that CT uses a mathmatical origin (i.e. 0,0 is bottom left like a cartisian plane)
/// - Parameters:
///   - context: A core graphics draw context
///   - attributedText: An attributed string
func drawText(context:CGContext, attributedText: NSAttributedString) {
    //Create our CT boiler plate
    let framesetter = CTFramesetterCreateWithAttributedString(attributedText)
    let textRect = bounds
    let path = CGPath(rect: textRect, transform: nil)
    let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil)

    //Fetch our lines, bridging to swift from CFArray
    let lines = CTFrameGetLines(frame) as [AnyObject]
    let lineCount = lines.count

    //Get the line origin coordinates. These are used for calculating stock line height (w/o baseline modifications)
    var lineOrigins = [CGPoint](repeating: CGPoint.zero, count: lineCount)
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), &lineOrigins);

    //Since we're starting from the bottom of the container we need get our bottom offset/padding (so text isn't slammed to the bottom or cut off)
    var ascent:CGFloat = 0
    var descent:CGFloat = 0
    var leading:CGFloat = 0
    if lineCount > 0 {
        CTLineGetTypographicBounds(lines.last as! CTLine, &ascent, &descent, &leading)
    }

    //This variable holds the current draw position, relative to CT origin of the bottom left
    //https://stackoverflow.com/a/27631737/1166266
    var drawYPositionFromOrigin:CGFloat = descent

    //Again, draw the lines in reverse so we don't need look ahead
    for lineIndex in (0..<lineCount).reversed()  {
        //Calculate the current line height so we can accurately move the position up later
        let lastLinePosition = lineIndex > 0 ? lineOrigins[lineIndex - 1].y: textRect.height
        let currentLineHeight = lastLinePosition - lineOrigins[lineIndex].y
        //Throughout the loop below this variable will be updated to the tallest value for the current line
        var maxLineHeight:CGFloat = currentLineHeight

        //Grab the current run glyph. This is used for attributed string interop
        let glyphRuns = CTLineGetGlyphRuns(lines[lineIndex] as! CTLine) as [AnyObject]

        for run in glyphRuns {
            let run = run as! CTRun
            //Convert the format range to something we can match to our string
            let runRange = CTRunGetStringRange(run)

            let attribuetsAtPosition = attributedText.attributes(at: runRange.location, effectiveRange: nil)
            var baselineAdjustment: CGFloat = 0.0
            if let adjust = attribuetsAtPosition[NSAttributedStringKey.baselineOffset] as? NSNumber {
                //We have a baseline offset!
                baselineAdjustment = CGFloat(adjust.floatValue)
            }

            //Check if this glyph run is tallest, and move it if it is
            maxLineHeight = max(currentLineHeight + baselineAdjustment, maxLineHeight)

            //Move the draw head. Note that we're drawing from the unupdated drawYPositionFromOrigin. This is again thanks to CT cartisian plane where we draw from the bottom left of text too.
            context.textPosition = CGPoint.init(x: lineOrigins[lineIndex].x, y: drawYPositionFromOrigin)
            //Draw!
            CTRunDraw(run, context, CFRangeMake(0, 0))

        }
        //Move our position because we've completed the drawing of the line which is at most `maxLineHeight`
        drawYPositionFromOrigin += maxLineHeight
    }
}

أنا أيضا الأسلوب الذي يحسب الارتفاع المطلوب من النص نظرا العرض.انها بالضبط نفس القانون إلا أنه لا رسم أي شيء.

/// Calculate the height if it were drawn using `drawText`
/// Uses the same code as drawText except it doesn't draw.
///
/// - Parameters:
///   - attributedText: The text to calculate the height of
///   - width: The constraining width
///   - estimationHeight: Optional paramater, default 30,000px. This is the container height used to layout the text. DO NOT USE CGFLOATMAX AS IT CORE TEXT CANNOT CREATE A FRAME OF THAT SIZE.
/// - Returns: The size required to fit the text
static func size(of attributedText:NSAttributedString,width:CGFloat, estimationHeight:CGFloat?=30000) -> CGSize {
    let framesetter = CTFramesetterCreateWithAttributedString(attributedText)
    let textRect = CGRect.init(x: 0, y: 0, width: width, height: estimationHeight!)
    let path = CGPath(rect: textRect, transform: nil)
    let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil)

    //Fetch our lines, bridging to swift from CFArray
    let lines = CTFrameGetLines(frame) as [AnyObject]
    let lineCount = lines.count

    //Get the line origin coordinates. These are used for calculating stock line height (w/o baseline modifications)
    var lineOrigins = [CGPoint](repeating: CGPoint.zero, count: lineCount)
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), &lineOrigins);

    //Since we're starting from the bottom of the container we need get our bottom offset/padding (so text isn't slammed to the bottom or cut off)
    var ascent:CGFloat = 0
    var descent:CGFloat = 0
    var leading:CGFloat = 0
    if lineCount > 0 {
        CTLineGetTypographicBounds(lines.last as! CTLine, &ascent, &descent, &leading)
    }

    //This variable holds the current draw position, relative to CT origin of the bottom left
    var drawYPositionFromOrigin:CGFloat = descent

    //Again, draw the lines in reverse so we don't need look ahead
    for lineIndex in (0..<lineCount).reversed()  {
        //Calculate the current line height so we can accurately move the position up later
        let lastLinePosition = lineIndex > 0 ? lineOrigins[lineIndex - 1].y: textRect.height
        let currentLineHeight = lastLinePosition - lineOrigins[lineIndex].y
        //Throughout the loop below this variable will be updated to the tallest value for the current line
        var maxLineHeight:CGFloat = currentLineHeight

        //Grab the current run glyph. This is used for attributed string interop
        let glyphRuns = CTLineGetGlyphRuns(lines[lineIndex] as! CTLine) as [AnyObject]

        for run in glyphRuns {
            let run = run as! CTRun
            //Convert the format range to something we can match to our string
            let runRange = CTRunGetStringRange(run)

            let attribuetsAtPosition = attributedText.attributes(at: runRange.location, effectiveRange: nil)
            var baselineAdjustment: CGFloat = 0.0
            if let adjust = attribuetsAtPosition[NSAttributedStringKey.baselineOffset] as? NSNumber {
                //We have a baseline offset!
                baselineAdjustment = CGFloat(adjust.floatValue)
            }

            //Check if this glyph run is tallest, and move it if it is
            maxLineHeight = max(currentLineHeight + baselineAdjustment, maxLineHeight)

            //Skip drawing since this is a height calculation
        }
        //Move our position because we've completed the drawing of the line which is at most `maxLineHeight`
        drawYPositionFromOrigin += maxLineHeight
    }
    return CGSize.init(width: width, height: drawYPositionFromOrigin)
}

مثل كل شيء وأنا أكتب ، كما فعلت بعض المعايير ضد بعض المكتبات العامة و وظائف النظام (على الرغم من أنها لن تعمل هنا).أنا استخدم ضخمة ، سلسلة معقدة هنا للحفاظ على أي شخص من أخذ عادل اختصارات.

---HEIGHT CALCULATION---
Runtime for 1000 iterations (ms) BoundsForRect: 5415.030002593994
Runtime for 1000 iterations (ms) layoutManager: 5370.990991592407
Runtime for 1000 iterations (ms) CTFramesetterSuggestFrameSizeWithConstraints: 2372.151017189026
Runtime for 1000 iterations (ms) CTFramesetterCreateFrame ObjC: 2300.302028656006
Runtime for 1000 iterations (ms) CTFramesetterCreateFrame-Swift: 2313.6669397354126
Runtime for 1000 iterations (ms) THIS ANSWER size(of:): 2566.351056098938


---RENDER---
Runtime for 1000 iterations (ms) AttributedLabel: 35.032033920288086
Runtime for 1000 iterations (ms) UILabel: 45.948028564453125
Runtime for 1000 iterations (ms) TTTAttributedLabel: 301.1329174041748
Runtime for 1000 iterations (ms) THIS ANSWER: 20.398974418640137

لذلك ملخص الوقت:عملنا بشكل جيد جدا! size(of...) هو تقريبا يساوي الأسهم CT تخطيط مما يعني أن لدينا الملحق مرتفع رخيصة إلى حد ما على الرغم من استخدام جدول تجزئة البحث.إلا أننا خارج الشقة الفوز على رسم المكالمات.وأظن أن هذا هو بسبب مكلفة جدا 30k بكسل تقدير الإطار علينا أن خلق.إذا جعلنا أفضل تقدير الأداء سيكون أفضل.لقد تم العمل لمدة ثلاث ساعات حتى أنا أتصل عليه إنهاء وترك ذلك باعتبارها ممارسة القارئ.

لقد ناضلت مع هذه المشكلة كذلك. اتضح ، كما اقترحت بعض الملصقات أعلاه ، أن أيا من الخطوط التي تأتي مع iOS تدعم الكائنات الفائقة أو الاشتراك. كان الحل الخاص بي هو شراء وتثبيت اثنين من خطوط SuperScript و SuperScript (كانت 9.99 دولار لكل منهما ، وهنا رابط للموقع http://superscriptfont.com/).

ليس من الصعب فعل ذلك. فقط أضف ملفات الخط كموارد وأضف إدخالات info.plist لـ "الخط المقدم بواسطة التطبيق".

كانت الخطوة التالية هي البحث عن العلامات المناسبة في nsattributedString ، وإزالة العلامات وتطبيق الخط على النص.

يعمل بشكل رائع!

تطور سريع 2 على إجابة ديميتري ؛ ينفذ بشكل فعال nsbaselineOffSetAttributEnam.

عند الترميز ، كنت في Uiview ، لذا كان لدي حدود معقولة للاستخدام. حسابه حساب المستقيم الخاص بها.

func drawText(context context:CGContextRef, attributedText: NSAttributedString) {

    // All this CoreText iteration just to add support for superscripting.
    // NSBaselineOffsetAttributeName isn't supported by CoreText. So we manully iterate through 
    // all the text ranges, rendering each, and offsetting the baseline where needed.

    let framesetter = CTFramesetterCreateWithAttributedString(attributedText)
    let textRect = CGRectOffset(bounds, 0, 0)
    let path = CGPathCreateWithRect(textRect, nil)
    let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil)

    // All the lines of text we'll render...
    let lines = CTFrameGetLines(frame) as [AnyObject]
    let lineCount = lines.count

    // And their origin coordinates...
    var lineOrigins = [CGPoint](count: lineCount, repeatedValue: CGPointZero)
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), &lineOrigins);

    for lineIndex in 0..<lineCount  {
        let lineObject = lines[lineIndex]

        // Each run of glyphs we'll render...
        let glyphRuns = CTLineGetGlyphRuns(lineObject as! CTLine) as [AnyObject]
        for r in glyphRuns {
            let run = r as! CTRun
            let runRange = CTRunGetStringRange(run)

            // What attributes are in the NSAttributedString here? If we find NSBaselineOffsetAttributeName, 
            // adjust the baseline.
            let attrs = attributedText.attributesAtIndex(runRange.location, effectiveRange: nil)
            var baselineAdjustment: CGFloat = 0.0
            if let adjust = attrs[NSBaselineOffsetAttributeName as String] as? NSNumber {
                baselineAdjustment = CGFloat(adjust.floatValue)
            }

            CGContextSetTextPosition(context, lineOrigins[lineIndex].x, lineOrigins[lineIndex].y - 25 + baselineAdjustment)

            CTRunDraw(run, context, CFRangeMake(0, 0))
        }
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top