Search in sources :

Example 26 with MetricAffectingSpan

use of android.text.style.MetricAffectingSpan in project android_frameworks_base by DirtyUnicorns.

the class MeasuredText method addStyleRun.

float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len, Paint.FontMetricsInt fm) {
    TextPaint workPaint = mWorkPaint;
    workPaint.set(paint);
    // XXX paint should not have a baseline shift, but...
    workPaint.baselineShift = 0;
    ReplacementSpan replacement = null;
    for (int i = 0; i < spans.length; i++) {
        MetricAffectingSpan span = spans[i];
        if (span instanceof ReplacementSpan) {
            replacement = (ReplacementSpan) span;
        } else {
            span.updateMeasureState(workPaint);
        }
    }
    float wid;
    if (replacement == null) {
        wid = addStyleRun(workPaint, len, fm);
    } else {
        // Use original text.  Shouldn't matter.
        wid = replacement.getSize(workPaint, mText, mTextStart + mPos, mTextStart + mPos + len, fm);
        if (mBuilder == null) {
            float[] w = mWidths;
            w[mPos] = wid;
            for (int i = mPos + 1, e = mPos + len; i < e; i++) w[i] = 0;
        } else {
            mBuilder.addReplacementRun(mPos, mPos + len, wid);
        }
        mPos += len;
    }
    if (fm != null) {
        if (workPaint.baselineShift < 0) {
            fm.ascent += workPaint.baselineShift;
            fm.top += workPaint.baselineShift;
        } else {
            fm.descent += workPaint.baselineShift;
            fm.bottom += workPaint.baselineShift;
        }
    }
    return wid;
}
Also used : ReplacementSpan(android.text.style.ReplacementSpan) Paint(android.graphics.Paint) MetricAffectingSpan(android.text.style.MetricAffectingSpan)

Example 27 with MetricAffectingSpan

use of android.text.style.MetricAffectingSpan in project android_frameworks_base by DirtyUnicorns.

the class StaticLayout method generate.

/* package */
void generate(Builder b, boolean includepad, boolean trackpad) {
    CharSequence source = b.mText;
    int bufStart = b.mStart;
    int bufEnd = b.mEnd;
    TextPaint paint = b.mPaint;
    int outerWidth = b.mWidth;
    TextDirectionHeuristic textDir = b.mTextDir;
    float spacingmult = b.mSpacingMult;
    float spacingadd = b.mSpacingAdd;
    float ellipsizedWidth = b.mEllipsizedWidth;
    TextUtils.TruncateAt ellipsize = b.mEllipsize;
    // TODO: move to builder to avoid allocation costs
    LineBreaks lineBreaks = new LineBreaks();
    // store span end locations
    int[] spanEndCache = new int[4];
    // store fontMetrics per span range
    // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
    int[] fmCache = new int[4 * 4];
    // TODO: also respect LocaleSpan within the text
    b.setLocale(paint.getTextLocale());
    mLineCount = 0;
    int v = 0;
    boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
    Paint.FontMetricsInt fm = b.mFontMetricsInt;
    int[] chooseHtv = null;
    MeasuredText measured = b.mMeasuredText;
    Spanned spanned = null;
    if (source instanceof Spanned)
        spanned = (Spanned) source;
    int paraEnd;
    for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
        paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
        if (paraEnd < 0)
            paraEnd = bufEnd;
        else
            paraEnd++;
        int firstWidthLineCount = 1;
        int firstWidth = outerWidth;
        int restWidth = outerWidth;
        LineHeightSpan[] chooseHt = null;
        if (spanned != null) {
            LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd, LeadingMarginSpan.class);
            for (int i = 0; i < sp.length; i++) {
                LeadingMarginSpan lms = sp[i];
                firstWidth -= sp[i].getLeadingMargin(true);
                restWidth -= sp[i].getLeadingMargin(false);
                // leading margin spans, not just this particular one
                if (lms instanceof LeadingMarginSpan2) {
                    LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
                    firstWidthLineCount = Math.max(firstWidthLineCount, lms2.getLeadingMarginLineCount());
                }
            }
            chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
            if (chooseHt.length == 0) {
                // So that out() would not assume it has any contents
                chooseHt = null;
            } else {
                if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
                    chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
                }
                for (int i = 0; i < chooseHt.length; i++) {
                    int o = spanned.getSpanStart(chooseHt[i]);
                    if (o < paraStart) {
                        // starts in this layout, before the
                        // current paragraph
                        chooseHtv[i] = getLineTop(getLineForOffset(o));
                    } else {
                        // starts in this paragraph
                        chooseHtv[i] = v;
                    }
                }
            }
        }
        measured.setPara(source, paraStart, paraEnd, textDir, b);
        char[] chs = measured.mChars;
        float[] widths = measured.mWidths;
        byte[] chdirs = measured.mLevels;
        int dir = measured.mDir;
        boolean easy = measured.mEasy;
        // tab stop locations
        int[] variableTabStops = null;
        if (spanned != null) {
            TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, paraEnd, TabStopSpan.class);
            if (spans.length > 0) {
                int[] stops = new int[spans.length];
                for (int i = 0; i < spans.length; i++) {
                    stops[i] = spans[i].getTabStop();
                }
                Arrays.sort(stops, 0, stops.length);
                variableTabStops = stops;
            }
        }
        nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart, firstWidth, firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency);
        if (mLeftIndents != null || mRightIndents != null) {
            // TODO(raph) performance: it would be better to do this once per layout rather
            // than once per paragraph, but that would require a change to the native
            // interface.
            int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
            int rightLen = mRightIndents == null ? 0 : mRightIndents.length;
            int indentsLen = Math.max(1, Math.max(leftLen, rightLen) - mLineCount);
            int[] indents = new int[indentsLen];
            for (int i = 0; i < indentsLen; i++) {
                int leftMargin = mLeftIndents == null ? 0 : mLeftIndents[Math.min(i + mLineCount, leftLen - 1)];
                int rightMargin = mRightIndents == null ? 0 : mRightIndents[Math.min(i + mLineCount, rightLen - 1)];
                indents[i] = leftMargin + rightMargin;
            }
            nSetIndents(b.mNativePtr, indents);
        }
        // measurement has to be done before performing line breaking
        // but we don't want to recompute fontmetrics or span ranges the
        // second time, so we cache those and then use those stored values
        int fmCacheCount = 0;
        int spanEndCacheCount = 0;
        for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
            if (fmCacheCount * 4 >= fmCache.length) {
                int[] grow = new int[fmCacheCount * 4 * 2];
                System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
                fmCache = grow;
            }
            if (spanEndCacheCount >= spanEndCache.length) {
                int[] grow = new int[spanEndCacheCount * 2];
                System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
                spanEndCache = grow;
            }
            if (spanned == null) {
                spanEnd = paraEnd;
                int spanLen = spanEnd - spanStart;
                measured.addStyleRun(paint, spanLen, fm);
            } else {
                spanEnd = spanned.nextSpanTransition(spanStart, paraEnd, MetricAffectingSpan.class);
                int spanLen = spanEnd - spanStart;
                MetricAffectingSpan[] spans = spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
                spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
                measured.addStyleRun(paint, spans, spanLen, fm);
            }
            // the order of storage here (top, bottom, ascent, descent) has to match the code below
            // where these values are retrieved
            fmCache[fmCacheCount * 4 + 0] = fm.top;
            fmCache[fmCacheCount * 4 + 1] = fm.bottom;
            fmCache[fmCacheCount * 4 + 2] = fm.ascent;
            fmCache[fmCacheCount * 4 + 3] = fm.descent;
            fmCacheCount++;
            spanEndCache[spanEndCacheCount] = spanEnd;
            spanEndCacheCount++;
        }
        nGetWidths(b.mNativePtr, widths);
        int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
        int[] breaks = lineBreaks.breaks;
        float[] lineWidths = lineBreaks.widths;
        int[] flags = lineBreaks.flags;
        final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
        final boolean ellipsisMayBeApplied = ellipsize != null && (ellipsize == TextUtils.TruncateAt.END || (mMaximumVisibleLineCount == 1 && ellipsize != TextUtils.TruncateAt.MARQUEE));
        if (remainingLineCount > 0 && remainingLineCount < breakCount && ellipsisMayBeApplied) {
            // Calculate width and flag.
            float width = 0;
            int flag = 0;
            for (int i = remainingLineCount - 1; i < breakCount; i++) {
                if (i == breakCount - 1) {
                    width += lineWidths[i];
                } else {
                    for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
                        width += widths[j];
                    }
                }
                flag |= flags[i] & TAB_MASK;
            }
            // Treat the last line and overflowed lines as a single line.
            breaks[remainingLineCount - 1] = breaks[breakCount - 1];
            lineWidths[remainingLineCount - 1] = width;
            flags[remainingLineCount - 1] = flag;
            breakCount = remainingLineCount;
        }
        // here is the offset of the starting character of the line we are currently measuring
        int here = paraStart;
        int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
        int fmCacheIndex = 0;
        int spanEndCacheIndex = 0;
        int breakIndex = 0;
        for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
            // retrieve end of span
            spanEnd = spanEndCache[spanEndCacheIndex++];
            // retrieve cached metrics, order matches above
            fm.top = fmCache[fmCacheIndex * 4 + 0];
            fm.bottom = fmCache[fmCacheIndex * 4 + 1];
            fm.ascent = fmCache[fmCacheIndex * 4 + 2];
            fm.descent = fmCache[fmCacheIndex * 4 + 3];
            fmCacheIndex++;
            if (fm.top < fmTop) {
                fmTop = fm.top;
            }
            if (fm.ascent < fmAscent) {
                fmAscent = fm.ascent;
            }
            if (fm.descent > fmDescent) {
                fmDescent = fm.descent;
            }
            if (fm.bottom > fmBottom) {
                fmBottom = fm.bottom;
            }
            // skip breaks ending before current span range
            while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
                breakIndex++;
            }
            while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
                int endPos = paraStart + breaks[breakIndex];
                boolean moreChars = (endPos < bufEnd);
                v = out(source, here, endPos, fmAscent, fmDescent, fmTop, fmBottom, v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex], needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad, chs, widths, paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint, moreChars);
                if (endPos < spanEnd) {
                    // preserve metrics for current span
                    fmTop = fm.top;
                    fmBottom = fm.bottom;
                    fmAscent = fm.ascent;
                    fmDescent = fm.descent;
                } else {
                    fmTop = fmBottom = fmAscent = fmDescent = 0;
                }
                here = endPos;
                breakIndex++;
                if (mLineCount >= mMaximumVisibleLineCount) {
                    return;
                }
            }
        }
        if (paraEnd == bufEnd)
            break;
    }
    if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) && mLineCount < mMaximumVisibleLineCount) {
        // Log.e("text", "output last " + bufEnd);
        measured.setPara(source, bufEnd, bufEnd, textDir, b);
        paint.getFontMetricsInt(fm);
        v = out(source, bufEnd, bufEnd, fm.ascent, fm.descent, fm.top, fm.bottom, v, spacingmult, spacingadd, null, null, fm, 0, needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd, includepad, trackpad, null, null, bufStart, ellipsize, ellipsizedWidth, 0, paint, false);
    }
}
Also used : LineHeightSpan(android.text.style.LineHeightSpan) TabStopSpan(android.text.style.TabStopSpan) Paint(android.graphics.Paint) Paint(android.graphics.Paint) LeadingMarginSpan2(android.text.style.LeadingMarginSpan.LeadingMarginSpan2) LeadingMarginSpan(android.text.style.LeadingMarginSpan) MetricAffectingSpan(android.text.style.MetricAffectingSpan)

Example 28 with MetricAffectingSpan

use of android.text.style.MetricAffectingSpan in project android_frameworks_base by ResurrectionRemix.

the class TextLine method handleRun.

/**
     * Utility function for handling a unidirectional run.  The run must not
     * contain tabs but can contain styles.
     *
     *
     * @param start the line-relative start of the run
     * @param measureLimit the offset to measure to, between start and limit inclusive
     * @param limit the limit of the run
     * @param runIsRtl true if the run is right-to-left
     * @param c the canvas, can be null
     * @param x the end of the run closest to the leading margin
     * @param top the top of the line
     * @param y the baseline
     * @param bottom the bottom of the line
     * @param fmi receives metrics information, can be null
     * @param needWidth true if the width is required
     * @return the signed width of the run based on the run direction; only
     * valid if needWidth is true
     */
private float handleRun(int start, int measureLimit, int limit, boolean runIsRtl, Canvas c, float x, int top, int y, int bottom, FontMetricsInt fmi, boolean needWidth) {
    // Case of an empty line, make sure we update fmi according to mPaint
    if (start == measureLimit) {
        TextPaint wp = mWorkPaint;
        wp.set(mPaint);
        if (fmi != null) {
            expandMetricsFromPaint(fmi, wp);
        }
        return 0f;
    }
    if (mSpanned == null) {
        TextPaint wp = mWorkPaint;
        wp.set(mPaint);
        final int mlimit = measureLimit;
        return handleText(wp, start, limit, start, limit, runIsRtl, c, x, top, y, bottom, fmi, needWidth || mlimit < measureLimit, mlimit);
    }
    mMetricAffectingSpanSpanSet.init(mSpanned, mStart + start, mStart + limit);
    mCharacterStyleSpanSet.init(mSpanned, mStart + start, mStart + limit);
    // Shaping needs to take into account context up to metric boundaries,
    // but rendering needs to take into account character style boundaries.
    // So we iterate through metric runs to get metric bounds,
    // then within each metric run iterate through character style runs
    // for the run bounds.
    final float originalX = x;
    for (int i = start, inext; i < measureLimit; i = inext) {
        TextPaint wp = mWorkPaint;
        wp.set(mPaint);
        inext = mMetricAffectingSpanSpanSet.getNextTransition(mStart + i, mStart + limit) - mStart;
        int mlimit = Math.min(inext, measureLimit);
        ReplacementSpan replacement = null;
        for (int j = 0; j < mMetricAffectingSpanSpanSet.numberOfSpans; j++) {
            // empty by construction. This special case in getSpans() explains the >= & <= tests
            if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit) || (mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i))
                continue;
            MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j];
            if (span instanceof ReplacementSpan) {
                replacement = (ReplacementSpan) span;
            } else {
                // We might have a replacement that uses the draw
                // state, otherwise measure state would suffice.
                span.updateDrawState(wp);
            }
        }
        if (replacement != null) {
            x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y, bottom, fmi, needWidth || mlimit < measureLimit);
            continue;
        }
        for (int j = i, jnext; j < mlimit; j = jnext) {
            jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + inext) - mStart;
            int offset = Math.min(jnext, mlimit);
            wp.set(mPaint);
            for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
                // Intentionally using >= and <= as explained above
                if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + offset) || (mCharacterStyleSpanSet.spanEnds[k] <= mStart + j))
                    continue;
                CharacterStyle span = mCharacterStyleSpanSet.spans[k];
                span.updateDrawState(wp);
            }
            // Only draw hyphen on last run in line
            if (jnext < mLen) {
                wp.setHyphenEdit(0);
            }
            x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x, top, y, bottom, fmi, needWidth || jnext < measureLimit, offset);
        }
    }
    return x - originalX;
}
Also used : ReplacementSpan(android.text.style.ReplacementSpan) Paint(android.graphics.Paint) MetricAffectingSpan(android.text.style.MetricAffectingSpan) CharacterStyle(android.text.style.CharacterStyle)

Example 29 with MetricAffectingSpan

use of android.text.style.MetricAffectingSpan in project android_frameworks_base by ResurrectionRemix.

the class MeasuredText method addStyleRun.

float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len, Paint.FontMetricsInt fm) {
    TextPaint workPaint = mWorkPaint;
    workPaint.set(paint);
    // XXX paint should not have a baseline shift, but...
    workPaint.baselineShift = 0;
    ReplacementSpan replacement = null;
    for (int i = 0; i < spans.length; i++) {
        MetricAffectingSpan span = spans[i];
        if (span instanceof ReplacementSpan) {
            replacement = (ReplacementSpan) span;
        } else {
            span.updateMeasureState(workPaint);
        }
    }
    float wid;
    if (replacement == null) {
        wid = addStyleRun(workPaint, len, fm);
    } else {
        // Use original text.  Shouldn't matter.
        wid = replacement.getSize(workPaint, mText, mTextStart + mPos, mTextStart + mPos + len, fm);
        if (mBuilder == null) {
            float[] w = mWidths;
            w[mPos] = wid;
            for (int i = mPos + 1, e = mPos + len; i < e; i++) w[i] = 0;
        } else {
            mBuilder.addReplacementRun(mPos, mPos + len, wid);
        }
        mPos += len;
    }
    if (fm != null) {
        if (workPaint.baselineShift < 0) {
            fm.ascent += workPaint.baselineShift;
            fm.top += workPaint.baselineShift;
        } else {
            fm.descent += workPaint.baselineShift;
            fm.bottom += workPaint.baselineShift;
        }
    }
    return wid;
}
Also used : ReplacementSpan(android.text.style.ReplacementSpan) Paint(android.graphics.Paint) MetricAffectingSpan(android.text.style.MetricAffectingSpan)

Example 30 with MetricAffectingSpan

use of android.text.style.MetricAffectingSpan in project android_frameworks_base by crdroidandroid.

the class TextLine method getOffsetBeforeAfter.

/**
     * Returns the next valid offset within this directional run, skipping
     * conjuncts and zero-width characters.  This should not be called to walk
     * off the end of the line, since the returned values might not be valid
     * on neighboring lines.  If the returned offset is less than zero or
     * greater than the line length, the offset should be recomputed on the
     * preceding or following line, respectively.
     *
     * @param runIndex the run index
     * @param runStart the start of the run
     * @param runLimit the limit of the run
     * @param runIsRtl true if the run is right-to-left
     * @param offset the offset
     * @param after true if the new offset should logically follow the provided
     * offset
     * @return the new offset
     */
private int getOffsetBeforeAfter(int runIndex, int runStart, int runLimit, boolean runIsRtl, int offset, boolean after) {
    if (runIndex < 0 || offset == (after ? mLen : 0)) {
        // return accurate values.  These are a guess.
        if (after) {
            return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
        }
        return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
    }
    TextPaint wp = mWorkPaint;
    wp.set(mPaint);
    int spanStart = runStart;
    int spanLimit;
    if (mSpanned == null) {
        spanLimit = runLimit;
    } else {
        int target = after ? offset + 1 : offset;
        int limit = mStart + runLimit;
        while (true) {
            spanLimit = mSpanned.nextSpanTransition(mStart + spanStart, limit, MetricAffectingSpan.class) - mStart;
            if (spanLimit >= target) {
                break;
            }
            spanStart = spanLimit;
        }
        MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart, mStart + spanLimit, MetricAffectingSpan.class);
        spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
        if (spans.length > 0) {
            ReplacementSpan replacement = null;
            for (int j = 0; j < spans.length; j++) {
                MetricAffectingSpan span = spans[j];
                if (span instanceof ReplacementSpan) {
                    replacement = (ReplacementSpan) span;
                } else {
                    span.updateMeasureState(wp);
                }
            }
            if (replacement != null) {
                // the start or end of this span.
                return after ? spanLimit : spanStart;
            }
        }
    }
    int dir = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
    int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
    if (mCharsValid) {
        return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart, dir, offset, cursorOpt);
    } else {
        return wp.getTextRunCursor(mText, mStart + spanStart, mStart + spanLimit, dir, mStart + offset, cursorOpt) - mStart;
    }
}
Also used : ReplacementSpan(android.text.style.ReplacementSpan) Paint(android.graphics.Paint) MetricAffectingSpan(android.text.style.MetricAffectingSpan)

Aggregations

MetricAffectingSpan (android.text.style.MetricAffectingSpan)32 Paint (android.graphics.Paint)30 ReplacementSpan (android.text.style.ReplacementSpan)22 CharacterStyle (android.text.style.CharacterStyle)7 LeadingMarginSpan (android.text.style.LeadingMarginSpan)7 LeadingMarginSpan2 (android.text.style.LeadingMarginSpan.LeadingMarginSpan2)7 LineHeightSpan (android.text.style.LineHeightSpan)7 TabStopSpan (android.text.style.TabStopSpan)7 Bitmap (android.graphics.Bitmap)2 TextPaint (android.text.TextPaint)2 Spanned (android.text.Spanned)1