use of com.bluejamesbond.text.style.DirectionSpan in project TextJustify-Android by bluejamesbond.
the class SpannableDocumentLayout method onDraw.
@Override
public void onDraw(Canvas canvas, int scrollTop, int scrollBottom) {
if (tokens.length < TOKEN_LENGTH) {
return;
}
Spannable textCpy = (Spannable) this.text;
int startIndex = getTokenForVertical(scrollTop, TokenPosition.START_OF_LINE);
int endIndex = getTokenForVertical(scrollBottom, TokenPosition.END_OF_LINE);
boolean defIsReverse = false;
for (LeadingMarginSpanDrawParameters parameters : mLeadMarginSpanDrawEvents) {
// FIXME sort by Y and break out of loop
int top = parameters.top - scrollTop;
int bottom = parameters.bottom - scrollTop;
if (bottom < 0 || top > scrollBottom)
continue;
parameters.span.drawLeadingMargin(canvas, paint, parameters.x, parameters.dir, top, parameters.baseline, bottom, textCpy, parameters.start, parameters.end, parameters.first, null);
}
int lastEndIndexY = tokens[endIndex + TOKEN_Y];
int diffEndIndexYCount = 1;
// FIXME Find next pos-y
for (int s = endIndex; diffEndIndexYCount > 0 && s < tokens.length; s += TOKEN_LENGTH) {
endIndex += TOKEN_LENGTH;
if (lastEndIndexY != tokens[s + TOKEN_Y]) {
diffEndIndexYCount--;
lastEndIndexY = tokens[s + TOKEN_Y];
}
}
for (int index = startIndex; index < endIndex; index += TOKEN_LENGTH) {
if (tokens[index + TOKEN_START] == Integer.MAX_VALUE)
break;
DirectionSpan[] directionSpans = textCpy.getSpans(tokens[index + TOKEN_START], tokens[index + TOKEN_END], DirectionSpan.class);
Styled.drawText(canvas, textCpy, tokens[index + TOKEN_START], tokens[index + TOKEN_END], Layout.DIR_LEFT_TO_RIGHT, directionSpans.length > 0 ? directionSpans[0].isReverse() : defIsReverse, tokens[index + TOKEN_X], 0, tokens[index + TOKEN_Y] - scrollTop, 0, paint, workPaint, false);
if (params.debugging) {
int lastColor = paint.getColor();
float lastStrokeWidth = paint.getStrokeWidth();
paint.setStrokeWidth(2);
paint.setColor(Color.GREEN);
canvas.drawLine(0, tokens[index + TOKEN_Y] - tokens[index + TOKEN_ASCENT] - scrollTop, params.parentWidth, tokens[index + TOKEN_Y] - tokens[index + TOKEN_ASCENT] - scrollTop, paint);
paint.setColor(Color.CYAN);
canvas.drawLine(0, tokens[index + TOKEN_Y] + tokens[index + TOKEN_DESCENT] - scrollTop, params.parentWidth, tokens[index + TOKEN_Y] + tokens[index + TOKEN_DESCENT] - scrollTop, paint);
paint.setColor(lastColor);
paint.setStrokeWidth(lastStrokeWidth);
}
}
}
use of com.bluejamesbond.text.style.DirectionSpan in project TextJustify-Android by bluejamesbond.
the class SpannableDocumentLayout method onMeasure.
@SuppressWarnings("ConstantConditions")
@Override
public boolean onMeasure(IProgress<Float> progress, ICancel<Boolean> cancelled) {
boolean done = true;
float parentWidth = params.getParentWidth();
float boundWidth = params.getParentWidth() - params.getInsetPaddingLeft() - params.getInsetPaddingRight();
mLeadMarginSpanDrawEvents = new LinkedList<>();
StaticLayout staticLayout = new StaticLayout(getText(), (TextPaint) getPaint(), (int) boundWidth, Layout.Alignment.ALIGN_NORMAL, 1, 0, false);
int[] newTokens = new int[TOKEN_LENGTH * 1000];
LeadingMarginSpan[] activeLeadSpans = new LeadingMarginSpan[0];
HashMap<LeadingMarginSpan, Integer> leadSpans = new HashMap<>();
TextAlignment defAlign = params.textAlignment;
Spannable textCpy = (Spannable) this.text;
Paint.FontMetricsInt fmi = paint.getFontMetricsInt();
int maxTextIndex = textCpy.length() - 1;
int lines = staticLayout.getLineCount();
int enableLineBreak = 0;
int index = 0;
int lineNumber;
float x;
float y = params.insetPaddingTop;
float left = params.insetPaddingLeft;
float right = params.insetPaddingRight;
float lineHeightAdd = params.lineHeightMultiplier;
float lastAscent;
float lastDescent;
boolean isParaStart = true;
boolean isReverse = params.reverse;
for (lineNumber = 0; lineNumber < lines; lineNumber++) {
if (cancelled.isCancelled()) {
done = false;
break;
}
progress.onUpdate((float) lineNumber / (float) lines);
newTokens = ammortizeArray(newTokens, index);
int start = staticLayout.getLineStart(lineNumber);
int end = staticLayout.getLineEnd(lineNumber);
float realWidth = boundWidth;
if (params.debugging) {
Console.log(start + " => " + end + " :: " + " " + -staticLayout.getLineAscent(lineNumber) + " " + staticLayout.getLineDescent(lineNumber) + " " + textCpy.subSequence(start, end).toString());
}
// start == end => end of textCpy
if (start == end || lineNumber >= params.maxLines) {
break;
}
// Get textCpy alignment for the line
TextAlignmentSpan[] textAlignmentSpans = textCpy.getSpans(start, end, TextAlignmentSpan.class);
TextAlignment lineTextAlignment = textAlignmentSpans.length == 0 ? defAlign : textAlignmentSpans[0].getTextAlignment();
// Calculate components of line height
lastAscent = -staticLayout.getLineAscent(lineNumber);
lastDescent = staticLayout.getLineDescent(lineNumber) + lineHeightAdd;
// Handle reverse
DirectionSpan[] directionSpans = textCpy.getSpans(start, end, DirectionSpan.class);
isReverse = directionSpans.length > 0 ? directionSpans[0].isReverse() : params.reverse;
// Line is ONLY a <br/> or \n
if (start + 1 == end && (Character.getNumericValue(textCpy.charAt(start)) == -1 || textCpy.charAt(start) == '\n')) {
// Line break indicates a new paragraph
// is next
isParaStart = true;
// Use the line-height of the next line
if (lineNumber + 1 < lines) {
y += enableLineBreak * (-staticLayout.getLineAscent(lineNumber + 1) + staticLayout.getLineDescent(lineNumber + 1));
}
// Don't ignore the next line breaks
enableLineBreak = 1;
continue;
} else {
// Ignore the next line break
enableLineBreak = 0;
}
x = lineTextAlignment == TextAlignment.RIGHT ? right : left;
y += lastAscent;
// Line CONTAINS a \n
boolean isParaEnd = end == maxTextIndex || textCpy.charAt(Math.min(end, maxTextIndex)) == '\n' || textCpy.charAt(end - 1) == '\n';
if (isParaEnd) {
enableLineBreak = 1;
}
// LeadingMarginSpan block
if (isParaStart) {
// Process LeadingMarginSpan
activeLeadSpans = textCpy.getSpans(start, end, LeadingMarginSpan.class);
// Set up all the spans
if (activeLeadSpans.length > 0) {
for (LeadingMarginSpan leadSpan : activeLeadSpans) {
if (!leadSpans.containsKey(leadSpan)) {
// Default margin is everything
int marginLineCount = -1;
if (leadSpan instanceof LeadingMarginSpan.LeadingMarginSpan2) {
LeadingMarginSpan.LeadingMarginSpan2 leadSpan2 = ((LeadingMarginSpan.LeadingMarginSpan2) leadSpan);
marginLineCount = leadSpan2.getLeadingMarginLineCount();
}
leadSpans.put(leadSpan, marginLineCount);
}
}
}
}
float totalMargin = 0.0f;
int top = (int) (y - lastAscent);
int baseline = (int) (y);
int bottom = (int) (y + lastDescent);
for (LeadingMarginSpan leadSpan : activeLeadSpans) {
// TOKEN_X based on alignment
float calcX = x;
// LineAlignment
int lineAlignmentVal = 1;
if (lineTextAlignment == TextAlignment.RIGHT) {
lineAlignmentVal = -1;
calcX = parentWidth - x;
}
// Get current line count
int spanLines = leadSpans.get(leadSpan);
// Update only if the valid next valid
if (spanLines > 0 || spanLines == -1) {
leadSpans.put(leadSpan, spanLines == -1 ? -1 : spanLines - 1);
mLeadMarginSpanDrawEvents.add(new LeadingMarginSpanDrawParameters(leadSpan, (int) calcX, lineAlignmentVal, top, baseline, bottom, start, end, isParaStart));
// Is margin required?
totalMargin += leadSpan.getLeadingMargin(isParaStart);
}
}
x += totalMargin;
realWidth -= totalMargin;
// Disable/enable new paragraph
isParaStart = isParaEnd;
// TextAlignmentSpan block
if (isParaEnd && lineTextAlignment == TextAlignment.JUSTIFIED) {
lineTextAlignment = isReverse ? TextAlignment.RIGHT : TextAlignment.LEFT;
}
if (params.debugging) {
Console.log(String.format("Align: %s, X: %fpx, Y: %fpx, PWidth: %fpx", lineTextAlignment, x, y, parentWidth));
}
switch(lineTextAlignment) {
case RIGHT:
{
float lineWidth = Styled.measureText(paint, workPaint, textCpy, start, end, fmi);
index = pushToken(newTokens, index, start, end, parentWidth - x - lineWidth, y, lastAscent, lastDescent, lineNumber);
y += lastDescent;
continue;
}
case CENTER:
{
float lineWidth = Styled.measureText(paint, workPaint, textCpy, start, end, fmi);
index = pushToken(newTokens, index, start, end, x + (realWidth - lineWidth) / 2, y, lastAscent, lastDescent, lineNumber);
y += lastDescent;
continue;
}
case LEFT:
{
index = pushToken(newTokens, index, start, end, x, y, lastAscent, lastDescent, lineNumber);
y += lastDescent;
continue;
}
}
// FIXME: Space at the end of each line, possibly due to scrollbar offset
// LinkedList<Integer> tokenized = tokenize(textCpy, start, end - 1);
// FIXME: 2016/4/16 Issue #105 bug
LinkedList<Integer> tokenized = tokenize(textCpy, start, end);
// If one long word without any spaces
if (tokenized.size() == 1) {
int stop = tokenized.get(0);
// characters individually
if (getTrimmedLength(textCpy, start, stop) != 0) {
float[] textWidths = new float[stop - start];
float sum = 0.0f, textsOffset = 0.0f, offset;
int m = 0;
Styled.getTextWidths(paint, workPaint, textCpy, start, stop, textWidths, fmi);
for (float tw : textWidths) {
sum += tw;
}
offset = (realWidth - sum) / (textWidths.length - 1);
for (int k = start; k < stop; k++) {
index = pushToken(newTokens, index, k, k + 1, x + textsOffset + (offset * m), y, lastAscent, lastDescent, lineNumber);
newTokens = ammortizeArray(newTokens, index);
textsOffset += textWidths[m++];
}
}
} else // Handle multiple words
{
int m = 1;
int indexOffset = 0;
int startIndex = index;
int reqSpaces = (tokenized.size() - 1) * TOKEN_LENGTH;
int rtlZero = 0;
float rtlRight = 0;
float rtlMul = 1;
float lineWidth = 0;
float offset;
if (isReverse) {
indexOffset = -2 * TOKEN_LENGTH;
rtlRight = parentWidth;
rtlMul = -1;
rtlZero = 1;
// reverse index
index += reqSpaces;
}
// more space
newTokens = ammortizeArray(newTokens, index + reqSpaces);
for (int stop : tokenized) {
float wordWidth = Styled.measureText(paint, workPaint, textCpy, start, stop, fmi);
// add word
index = pushToken(newTokens, index, start, stop, rtlRight + rtlMul * (x + lineWidth + rtlZero * wordWidth), y, lastAscent, lastDescent, lineNumber);
lineWidth += wordWidth;
start = stop + 1;
// based on if rtl
index += indexOffset;
}
if (isReverse) {
index = startIndex + reqSpaces + TOKEN_LENGTH;
}
offset = (realWidth - lineWidth) / (float) (tokenized.size() - 1);
if (isReverse) {
for (int pos = index - TOKEN_LENGTH * 2; pos >= startIndex; pos -= TOKEN_LENGTH) {
newTokens[pos + TOKEN_X] = (int) (((float) newTokens[pos + TOKEN_X]) - (offset * (float) m++));
}
} else {
for (int pos = startIndex + TOKEN_LENGTH; pos < index; pos += TOKEN_LENGTH) {
newTokens[pos + TOKEN_X] = (int) (((float) newTokens[pos + TOKEN_X]) + (offset * (float) m++));
}
}
}
y += lastDescent;
}
lineCount = lineNumber;
tokens = newTokens;
params.changed = false;
textChange = !done;
measuredHeight = (int) (y - lineHeightAdd + params.insetPaddingBottom);
return done;
}
use of com.bluejamesbond.text.style.DirectionSpan in project TextJustify-Android by bluejamesbond.
the class MixedRTLTest method onCreate.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addDocumentView(new ArticleBuilder().append(testName, false, new RelativeSizeSpan(2f), new StyleSpan(Typeface.BOLD), new LeftSpan()).append("<font color=0xFFC801>Justin Worland</font><font color=0x888888> @justinworland Oct. 25, 2014</font>", false, new RelativeSizeSpan(0.8f), new StyleSpan(Typeface.BOLD), new DirectionSpan(Direction.RIGHT_TO_LEFT)).append("<font color=0x888888>Updated: Oct. 25, 2014 2:34 PM</font>".toUpperCase(), true, new RelativeSizeSpan(0.6f), new StyleSpan(Typeface.BOLD)).append("State health department staff will be on the ground at state airports", true, new RelativeSizeSpan(1.2f), new StyleSpan(Typeface.BOLD), new StyleSpan(Typeface.ITALIC)).append("Healthcare workers returning to New York or New Jersey after treating Ebola patients in West Africa will be placed under a mandatory quarantine, officials announced Friday, one day after a Doctors Without Borders doctor was diagnosed with the virus in New York City. Illinois announced a similar policy Saturday, meaning it will be enforced in states with three of the five airports through which passengers traveling from the Ebola-stricken West African countries must enter the United States.", true, new RelativeSizeSpan(1f), new JustifiedSpan(), new DirectionSpan(Direction.RIGHT_TO_LEFT)).append("N.J. Gov. Chris Christie and N.Y. Gov. Andrew Cuomo made the announcement as part of a broader procedural plan to help protect the densely packed, highly populated area from any further spread of the disease.", true, new RelativeSizeSpan(1f), new RightSpan(), new DirectionSpan(Direction.RIGHT_TO_LEFT)).append("“Since taking office, I have erred on the side of caution when it comes to the safety and protection of New Yorkers, and the current situation regarding Ebola will be no different,” Gov. Cuomo said. “The steps New York and New Jersey are taking today will strengthen our safeguards to protect our residents against this disease and help ensure those that may be infected by Ebola are treated with the highest precautions.”", true, new RelativeSizeSpan(1f), new JustifiedSpan(), new StyleSpan(Typeface.ITALIC)).append("New York and New Jersey state health department staff will be present on the ground at John F. Kennedy International Airport in New York and Newark Liberty Airport in New Jersey. In addition to implementing the mandatory quarantine of health care workers and others who had direct contact with Ebola patients, health department officials in each state will determine whether others should travelers should be hospitalized or quarantined.", true, new RelativeSizeSpan(1f), new JustifiedSpan(), new StyleSpan(Typeface.ITALIC)).append("“The announcements mark a dramatic escalation in measures designed to prevent the spread of Ebola in the United States. Previously, only individuals with symptoms of Ebola would be quarantined upon entry to the U.S. under a federal rule from the Centers for Diseases Control and the Department of Homeland Security.”", false, new RelativeSizeSpan(1f), new JustifiedSpan()), DocumentView.FORMATTED_TEXT);
}
Aggregations