/* * Copyright (C) 2015-2017 Emanuel Moecklin * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.onegravity.rteditor; import android.text.SpannableString; import android.text.Spanned; import android.text.style.CharacterStyle; import android.text.style.ParagraphStyle; import java.lang.reflect.Array; /** * Clones the Spannable part of an editor by copying the text, all * CharacterStyle, and all ParagraphStyle spans to a new Spannable object * (used for undo/redo). * <p> * The code is partly taken from the non-public class * android.text.SpannableStringInternal. */ public class ClonedSpannableString extends SpannableString { private Object[] mSpans; private int[] mSpanData; private int mSpanCount; private static final int START = 0; private static final int END = 1; private static final int FLAGS = 2; private static final int COLUMNS = 3; public ClonedSpannableString(Spanned source) { this((CharSequence) source); } public ClonedSpannableString(CharSequence source) { super(source.toString()); // the toString is important to prevent the super class from copying the spans init(source, 0, source.length()); } private void init(CharSequence source, int start, int end) { int initial = 20; mSpans = new Object[initial]; mSpanData = new int[initial * 3]; if (source instanceof Spanned) { Spanned sp = (Spanned) source; for (Object span : sp.getSpans(start, end, Object.class)) { if (span instanceof CharacterStyle || span instanceof ParagraphStyle) { int st = sp.getSpanStart(span); int en = sp.getSpanEnd(span); int fl = sp.getSpanFlags(span); if (st < start) st = start; if (en > end) en = end; setSpan(span, st - start, en - start, fl); } } } } // ****************************************** SpannableString Methods ******************************************* @Override public void setSpan(Object what, int start, int end, int flags) { if (mSpanCount + 1 >= mSpans.length) { int newsize = mSpanCount + 10; Object[] newtags = new Object[newsize]; int[] newdata = new int[newsize * 3]; System.arraycopy(mSpans, 0, newtags, 0, mSpanCount); System.arraycopy(mSpanData, 0, newdata, 0, mSpanCount * 3); mSpans = newtags; mSpanData = newdata; } mSpans[mSpanCount] = what; mSpanData[mSpanCount * COLUMNS + START] = start; mSpanData[mSpanCount * COLUMNS + END] = end; mSpanData[mSpanCount * COLUMNS + FLAGS] = flags; mSpanCount++; } @Override public void removeSpan(Object what) { int count = mSpanCount; Object[] spans = mSpans; int[] data = mSpanData; for (int i = count - 1; i >= 0; i--) { if (spans[i] == what) { int c = count - (i + 1); System.arraycopy(spans, i + 1, spans, i, c); System.arraycopy(data, (i + 1) * COLUMNS, data, i * COLUMNS, c * COLUMNS); mSpanCount--; return; } } } public int getSpanStart(Object what) { int count = mSpanCount; Object[] spans = mSpans; int[] data = mSpanData; for (int i = count - 1; i >= 0; i--) { if (spans[i] == what) { return data[i * COLUMNS + START]; } } return -1; } public int getSpanEnd(Object what) { int count = mSpanCount; Object[] spans = mSpans; int[] data = mSpanData; for (int i = count - 1; i >= 0; i--) { if (spans[i] == what) { return data[i * COLUMNS + END]; } } return -1; } public int getSpanFlags(Object what) { int count = mSpanCount; Object[] spans = mSpans; int[] data = mSpanData; for (int i = count - 1; i >= 0; i--) { if (spans[i] == what) { return data[i * COLUMNS + FLAGS]; } } return 0; } @SuppressWarnings("unchecked") public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) { int count = 0; int spanCount = mSpanCount; Object[] spans = mSpans; int[] data = mSpanData; Object[] ret = null; Object ret1 = null; for (int i = 0; i < spanCount; i++) { if (kind != null && !kind.isInstance(spans[i])) { continue; } int spanStart = data[i * COLUMNS + START]; int spanEnd = data[i * COLUMNS + END]; if (spanStart > queryEnd) { continue; } if (spanEnd < queryStart) { continue; } if (spanStart != spanEnd && queryStart != queryEnd) { if (spanStart == queryEnd) { continue; } if (spanEnd == queryStart) { continue; } } if (count == 0) { ret1 = spans[i]; count++; } else { if (count == 1) { ret = (Object[]) Array.newInstance(kind, spanCount - i + 1); ret[0] = ret1; } int prio = data[i * COLUMNS + FLAGS] & Spanned.SPAN_PRIORITY; if (prio != 0) { int j; for (j = 0; j < count; j++) { int p = getSpanFlags(ret[j]) & Spanned.SPAN_PRIORITY; if (prio > p) { break; } } System.arraycopy(ret, j, ret, j + 1, count - j); ret[j] = spans[i]; count++; } else { ret[count++] = spans[i]; } } } if (count == 0) { return (T[]) Array.newInstance(kind, 0); } if (count == 1) { ret = (Object[]) Array.newInstance(kind, 1); ret[0] = ret1; return (T[]) ret; } if (count == ret.length) { return (T[]) ret; } Object[] nret = (Object[]) Array.newInstance(kind, count); System.arraycopy(ret, 0, nret, 0, count); return (T[]) nret; } @SuppressWarnings("rawtypes") public int nextSpanTransition(int start, int limit, Class kind) { int count = mSpanCount; Object[] spans = mSpans; int[] data = mSpanData; if (kind == null) { kind = Object.class; } for (int i = 0; i < count; i++) { int st = data[i * COLUMNS + START]; int en = data[i * COLUMNS + END]; if (st > start && st < limit && kind.isInstance(spans[i])) limit = st; if (en > start && en < limit && kind.isInstance(spans[i])) limit = en; } return limit; } }