/*
* 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;
}
}