diff options
Diffstat (limited to 'tools')
11 files changed, 779 insertions, 64 deletions
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java index 7b07404..8ffd1d8 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -251,7 +251,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static int getFlags(Paint thisPaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return 0; } @@ -264,7 +264,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static void setFlags(Paint thisPaint, int flags) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return; } @@ -280,7 +280,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static int getHinting(Paint thisPaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return Paint.HINTING_ON; } @@ -291,7 +291,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static void setHinting(Paint thisPaint, int mode) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return; } @@ -337,7 +337,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static int getColor(Paint thisPaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return 0; } @@ -348,7 +348,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static void setColor(Paint thisPaint, int color) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return; } @@ -359,7 +359,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static int getAlpha(Paint thisPaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return 0; } @@ -370,7 +370,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static void setAlpha(Paint thisPaint, int a) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return; } @@ -381,7 +381,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static float getStrokeWidth(Paint thisPaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return 1.f; } @@ -392,7 +392,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static void setStrokeWidth(Paint thisPaint, float width) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return; } @@ -403,7 +403,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static float getStrokeMiter(Paint thisPaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return 1.f; } @@ -414,7 +414,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static void setStrokeMiter(Paint thisPaint, float miter) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return; } @@ -441,14 +441,14 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static boolean isElegantTextHeight(Paint thisPaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); return delegate != null && delegate.mFontVariant == FontVariant.ELEGANT; } @LayoutlibDelegate /*package*/ static void setElegantTextHeight(Paint thisPaint, boolean elegant) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return; } @@ -459,7 +459,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static float getTextSize(Paint thisPaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return 1.f; } @@ -470,7 +470,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static void setTextSize(Paint thisPaint, float textSize) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return; } @@ -482,7 +482,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static float getTextScaleX(Paint thisPaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return 1.f; } @@ -493,7 +493,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static void setTextScaleX(Paint thisPaint, float scaleX) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return; } @@ -505,7 +505,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static float getTextSkewX(Paint thisPaint) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return 1.f; } @@ -516,7 +516,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static void setTextSkewX(Paint thisPaint, float skewX) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return; } @@ -528,7 +528,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static float ascent(Paint thisPaint) { // get the delegate - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return 0; } @@ -545,7 +545,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static float descent(Paint thisPaint) { // get the delegate - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return 0; } @@ -562,7 +562,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static float getFontMetrics(Paint thisPaint, FontMetrics metrics) { // get the delegate - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return 0; } @@ -573,7 +573,7 @@ public class Paint_Delegate { @LayoutlibDelegate /*package*/ static int getFontMetricsInt(Paint thisPaint, FontMetricsInt fmi) { // get the delegate - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return 0; } @@ -599,7 +599,7 @@ public class Paint_Delegate { /*package*/ static float native_measureText(Paint thisPaint, char[] text, int index, int count, int bidiFlags) { // get the delegate - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return 0; } @@ -1232,7 +1232,7 @@ public class Paint_Delegate { private static void setFlag(Paint thisPaint, int flagMask, boolean flagValue) { // get the delegate from the native int. - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance()); if (delegate == null) { return; } diff --git a/tools/layoutlib/bridge/src/android/text/GreedyLineBreaker.java b/tools/layoutlib/bridge/src/android/text/GreedyLineBreaker.java new file mode 100644 index 0000000..24e4b54 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/text/GreedyLineBreaker.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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 android.text; + +import com.android.annotations.NonNull; + +import android.text.Primitive.PrimitiveType; +import android.text.StaticLayout.LineBreaks; + +import java.util.ArrayList; +import java.util.List; + +import static android.text.Primitive.PrimitiveType.PENALTY_INFINITY; + +// Based on the native implementation of GreedyLineBreaker in +// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260 +public class GreedyLineBreaker extends LineBreaker { + + public GreedyLineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth, + @NonNull TabStops tabStops) { + super(primitives, lineWidth, tabStops); + } + + @Override + public void computeBreaks(LineBreaks lineBreaks) { + BreakInfo breakInfo = new BreakInfo(); + int lineNum = 0; + float width = 0, printedWidth = 0; + boolean breakFound = false, goodBreakFound = false; + int breakIndex = 0, goodBreakIndex = 0; + float breakWidth = 0, goodBreakWidth = 0; + int firstTabIndex = Integer.MAX_VALUE; + + float maxWidth = mLineWidth.getLineWidth(lineNum); + + int numPrimitives = mPrimitives.size(); + // greedily fit as many characters as possible on each line + // loop over all primitives, and choose the best break point + // (if possible, a break point without splitting a word) + // after going over the maximum length + for (int i = 0; i < numPrimitives; i++) { + Primitive p = mPrimitives.get(i); + + // update the current line width + if (p.type == PrimitiveType.BOX || p.type == PrimitiveType.GLUE) { + width += p.width; + if (p.type == PrimitiveType.BOX) { + printedWidth = width; + } + } else if (p.type == PrimitiveType.VARIABLE) { + width = mTabStops.width(width); + // keep track of first tab character in the region we are examining + // so we can determine whether or not a line contains a tab + firstTabIndex = Math.min(firstTabIndex, i); + } + + // find the best break point for the characters examined so far + if (printedWidth > maxWidth) { + //noinspection StatementWithEmptyBody + if (breakFound || goodBreakFound) { + if (goodBreakFound) { + // a true line break opportunity existed in the characters examined so far, + // so there is no need to split a word + i = goodBreakIndex; // no +1 because of i++ + lineNum++; + maxWidth = mLineWidth.getLineWidth(lineNum); + breakInfo.mBreaksList.add(mPrimitives.get(goodBreakIndex).location); + breakInfo.mWidthsList.add(goodBreakWidth); + breakInfo.mFlagsList.add(firstTabIndex < goodBreakIndex); + firstTabIndex = Integer.MAX_VALUE; + } else { + // must split a word because there is no other option + i = breakIndex; // no +1 because of i++ + lineNum++; + maxWidth = mLineWidth.getLineWidth(lineNum); + breakInfo.mBreaksList.add(mPrimitives.get(breakIndex).location); + breakInfo.mWidthsList.add(breakWidth); + breakInfo.mFlagsList.add(firstTabIndex < breakIndex); + firstTabIndex = Integer.MAX_VALUE; + } + printedWidth = width = 0; + goodBreakFound = breakFound = false; + goodBreakWidth = breakWidth = 0; + continue; + } else { + // no choice, keep going... must make progress by putting at least one + // character on a line, even if part of that character is cut off -- + // there is no other option + } + } + + // update possible break points + if (p.type == PrimitiveType.PENALTY && + p.penalty < PENALTY_INFINITY) { + // this does not handle penalties with width + + // handle forced line break + if (p.penalty == -PENALTY_INFINITY) { + lineNum++; + maxWidth = mLineWidth.getLineWidth(lineNum); + breakInfo.mBreaksList.add(p.location); + breakInfo.mWidthsList.add(printedWidth); + breakInfo.mFlagsList.add(firstTabIndex < i); + firstTabIndex = Integer.MAX_VALUE; + printedWidth = width = 0; + goodBreakFound = breakFound = false; + goodBreakWidth = breakWidth = 0; + continue; + } + if (i > breakIndex && (printedWidth <= maxWidth || !breakFound)) { + breakFound = true; + breakIndex = i; + breakWidth = printedWidth; + } + if (i > goodBreakIndex && printedWidth <= maxWidth) { + goodBreakFound = true; + goodBreakIndex = i; + goodBreakWidth = printedWidth; + } + } else if (p.type == PrimitiveType.WORD_BREAK) { + // only do this if necessary -- we don't want to break words + // when possible, but sometimes it is unavoidable + if (i > breakIndex && (printedWidth <= maxWidth || !breakFound)) { + breakFound = true; + breakIndex = i; + breakWidth = printedWidth; + } + } + } + + if (breakFound || goodBreakFound) { + // output last break if there are more characters to output + if (goodBreakFound) { + breakInfo.mBreaksList.add(mPrimitives.get(goodBreakIndex).location); + breakInfo.mWidthsList.add(goodBreakWidth); + breakInfo.mFlagsList.add(firstTabIndex < goodBreakIndex); + } else { + breakInfo.mBreaksList.add(mPrimitives.get(breakIndex).location); + breakInfo.mWidthsList.add(breakWidth); + breakInfo.mFlagsList.add(firstTabIndex < breakIndex); + } + } + breakInfo.copyTo(lineBreaks); + } + + private static class BreakInfo { + List<Integer> mBreaksList = new ArrayList<Integer>(); + List<Float> mWidthsList = new ArrayList<Float>(); + List<Boolean> mFlagsList = new ArrayList<Boolean>(); + + public void copyTo(LineBreaks lineBreaks) { + if (lineBreaks.breaks.length != mBreaksList.size()) { + lineBreaks.breaks = new int[mBreaksList.size()]; + lineBreaks.widths = new float[mWidthsList.size()]; + lineBreaks.flags = new boolean[mFlagsList.size()]; + } + + int i = 0; + for (int b : mBreaksList) { + lineBreaks.breaks[i] = b; + i++; + } + i = 0; + for (float b : mWidthsList) { + lineBreaks.widths[i] = b; + i++; + } + i = 0; + for (boolean b : mFlagsList) { + lineBreaks.flags[i] = b; + i++; + } + + mBreaksList = null; + mWidthsList = null; + mFlagsList = null; + } + } +} diff --git a/tools/layoutlib/bridge/src/android/text/LineBreaker.java b/tools/layoutlib/bridge/src/android/text/LineBreaker.java new file mode 100644 index 0000000..8be3635 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/text/LineBreaker.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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 android.text; + +import android.text.StaticLayout.LineBreaks; +import com.android.annotations.NonNull; + +import java.util.Collections; +import java.util.List; + +// Based on the native implementation of LineBreaker in +// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260 +public abstract class LineBreaker { + + protected final @NonNull List<Primitive> mPrimitives; + protected final @NonNull LineWidth mLineWidth; + protected final @NonNull TabStops mTabStops; + + public LineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth, + @NonNull TabStops tabStops) { + mPrimitives = Collections.unmodifiableList(primitives); + mLineWidth = lineWidth; + mTabStops = tabStops; + } + + @NonNull + public abstract void computeBreaks(@NonNull LineBreaks breakInfo); +} diff --git a/tools/layoutlib/bridge/src/android/text/LineWidth.java b/tools/layoutlib/bridge/src/android/text/LineWidth.java new file mode 100644 index 0000000..2ea886d --- /dev/null +++ b/tools/layoutlib/bridge/src/android/text/LineWidth.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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 android.text; + +// Based on the native implementation of LineWidth in +// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260 +public class LineWidth { + private final float mFirstWidth; + private final int mFirstWidthLineCount; + private float mRestWidth; + + public LineWidth(float firstWidth, int firstWidthLineCount, float restWidth) { + mFirstWidth = firstWidth; + mFirstWidthLineCount = firstWidthLineCount; + mRestWidth = restWidth; + } + + public float getLineWidth(int line) { + return (line < mFirstWidthLineCount) ? mFirstWidth : mRestWidth; + } +} diff --git a/tools/layoutlib/bridge/src/android/text/OptimizingLineBreaker.java b/tools/layoutlib/bridge/src/android/text/OptimizingLineBreaker.java new file mode 100644 index 0000000..d5d7798 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/text/OptimizingLineBreaker.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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 android.text; + +import com.android.annotations.NonNull; + +import android.text.Primitive.PrimitiveType; +import android.text.StaticLayout.LineBreaks; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +import static android.text.Primitive.PrimitiveType.PENALTY_INFINITY; + + +// Based on the native implementation of OptimizingLineBreaker in +// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260 +/** + * A more complex version of line breaking where we try to prevent the right edge from being too + * jagged. + */ +public class OptimizingLineBreaker extends LineBreaker { + + public OptimizingLineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth, + @NonNull TabStops tabStops) { + super(primitives, lineWidth, tabStops); + } + + @Override + public void computeBreaks(@NonNull LineBreaks breakInfo) { + int numBreaks = mPrimitives.size(); + assert numBreaks > 0; + if (numBreaks == 1) { + // This can be true only if it's an empty paragraph. + Primitive p = mPrimitives.get(0); + assert p.type == PrimitiveType.PENALTY; + breakInfo.breaks = new int[]{0}; + breakInfo.widths = new float[]{p.width}; + breakInfo.flags = new boolean[]{false}; + return; + } + Node[] opt = new Node[numBreaks]; + opt[0] = new Node(-1, 0, 0, 0, false); + opt[numBreaks - 1] = new Node(-1, 0, 0, 0, false); + + ArrayList<Integer> active = new ArrayList<Integer>(); + active.add(0); + int lastBreak = 0; + for (int i = 0; i < numBreaks; i++) { + Primitive p = mPrimitives.get(i); + if (p.type == PrimitiveType.PENALTY) { + boolean finalBreak = (i + 1 == numBreaks); + Node bestBreak = null; + + for (ListIterator<Integer> it = active.listIterator(); it.hasNext(); + /* incrementing done in loop */) { + int pos = it.next(); + int lines = opt[pos].mPrevCount; + float maxWidth = mLineWidth.getLineWidth(lines); + // we have to compute metrics every time -- + // we can't really pre-compute this stuff and just deal with breaks + // because of the way tab characters work, this makes it computationally + // harder, but this way, we can still optimize while treating tab characters + // correctly + LineMetrics lineMetrics = computeMetrics(pos, i); + if (lineMetrics.mPrintedWidth <= maxWidth) { + float demerits = computeDemerits(maxWidth, lineMetrics.mPrintedWidth, + finalBreak, p.penalty) + opt[pos].mDemerits; + if (bestBreak == null || demerits < bestBreak.mDemerits) { + if (bestBreak == null) { + bestBreak = new Node(pos, opt[pos].mPrevCount + 1, demerits, + lineMetrics.mPrintedWidth, lineMetrics.mHasTabs); + } else { + bestBreak.mPrev = pos; + bestBreak.mPrevCount = opt[pos].mPrevCount + 1; + bestBreak.mDemerits = demerits; + bestBreak.mWidth = lineMetrics.mPrintedWidth; + bestBreak.mHasTabs = lineMetrics.mHasTabs; + } + } + } else { + it.remove(); + } + } + if (p.penalty == -PENALTY_INFINITY) { + active.clear(); + } + if (bestBreak != null) { + opt[i] = bestBreak; + active.add(i); + lastBreak = i; + } + if (active.isEmpty()) { + // we can't give up! + LineMetrics lineMetrics = new LineMetrics(); + int lines = opt[lastBreak].mPrevCount; + float maxWidth = mLineWidth.getLineWidth(lines); + int breakIndex = desperateBreak(lastBreak, numBreaks, maxWidth, lineMetrics); + opt[breakIndex] = new Node(lastBreak, lines + 1, 0 /*doesn't matter*/, + lineMetrics.mWidth, lineMetrics.mHasTabs); + active.add(breakIndex); + lastBreak = breakIndex; + i = breakIndex; // incremented by i++ + } + } + } + + int idx = numBreaks - 1; + int count = opt[idx].mPrevCount; + resize(breakInfo, count); + while (opt[idx].mPrev != -1) { + count--; + assert count >=0; + + breakInfo.breaks[count] = mPrimitives.get(idx).location; + breakInfo.widths[count] = opt[idx].mWidth; + breakInfo.flags [count] = opt[idx].mHasTabs; + idx = opt[idx].mPrev; + } + } + + private static void resize(LineBreaks lineBreaks, int size) { + if (lineBreaks.breaks.length == size) { + return; + } + int[] breaks = new int[size]; + float[] widths = new float[size]; + boolean[] flags = new boolean[size]; + + int toCopy = Math.min(size, lineBreaks.breaks.length); + System.arraycopy(lineBreaks.breaks, 0, breaks, 0, toCopy); + System.arraycopy(lineBreaks.widths, 0, widths, 0, toCopy); + System.arraycopy(lineBreaks.flags, 0, flags, 0, toCopy); + + lineBreaks.breaks = breaks; + lineBreaks.widths = widths; + lineBreaks.flags = flags; + } + + @NonNull + private LineMetrics computeMetrics(int start, int end) { + boolean f = false; + float w = 0, pw = 0; + for (int i = start; i < end; i++) { + Primitive p = mPrimitives.get(i); + if (p.type == PrimitiveType.BOX || p.type == PrimitiveType.GLUE) { + w += p.width; + if (p.type == PrimitiveType.BOX) { + pw = w; + } + } else if (p.type == PrimitiveType.VARIABLE) { + w = mTabStops.width(w); + f = true; + } + } + return new LineMetrics(w, pw, f); + } + + private static float computeDemerits(float maxWidth, float width, boolean finalBreak, + float penalty) { + float deviation = finalBreak ? 0 : maxWidth - width; + return (deviation * deviation) + penalty; + } + + /** + * @return the last break position or -1 if failed. + */ + @SuppressWarnings("ConstantConditions") // method too complex to be analyzed. + private int desperateBreak(int start, int limit, float maxWidth, + @NonNull LineMetrics lineMetrics) { + float w = 0, pw = 0; + boolean breakFound = false; + int breakIndex = 0, firstTabIndex = Integer.MAX_VALUE; + for (int i = start; i < limit; i++) { + Primitive p = mPrimitives.get(i); + + if (p.type == PrimitiveType.BOX || p.type == PrimitiveType.GLUE) { + w += p.width; + if (p.type == PrimitiveType.BOX) { + pw = w; + } + } else if (p.type == PrimitiveType.VARIABLE) { + w = mTabStops.width(w); + firstTabIndex = Math.min(firstTabIndex, i); + } + + if (pw > maxWidth && breakFound) { + break; + } + + // must make progress + if (i > start && + (p.type == PrimitiveType.PENALTY || p.type == PrimitiveType.WORD_BREAK)) { + breakFound = true; + breakIndex = i; + } + } + + if (breakFound) { + lineMetrics.mWidth = w; + lineMetrics.mPrintedWidth = pw; + lineMetrics.mHasTabs = (start <= firstTabIndex && firstTabIndex < breakIndex); + return breakIndex; + } else { + return -1; + } + } + + private static class LineMetrics { + /** Actual width of the line. */ + float mWidth; + /** Width of the line minus trailing whitespace. */ + float mPrintedWidth; + boolean mHasTabs; + + public LineMetrics() { + } + + public LineMetrics(float width, float printedWidth, boolean hasTabs) { + mWidth = width; + mPrintedWidth = printedWidth; + mHasTabs = hasTabs; + } + } + + /** + * A struct to store the info about a break. + */ + @SuppressWarnings("SpellCheckingInspection") // For the word struct. + private static class Node { + // -1 for the first node. + int mPrev; + // number of breaks so far. + int mPrevCount; + float mDemerits; + float mWidth; + boolean mHasTabs; + + public Node(int prev, int prevCount, float demerits, float width, boolean hasTabs) { + mPrev = prev; + mPrevCount = prevCount; + mDemerits = demerits; + mWidth = width; + mHasTabs = hasTabs; + } + } +} diff --git a/tools/layoutlib/bridge/src/android/text/Primitive.java b/tools/layoutlib/bridge/src/android/text/Primitive.java new file mode 100644 index 0000000..ce77601 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/text/Primitive.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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 android.text; + +import com.android.annotations.NonNull; + +// Based on the native implementation of Primitive in +// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260 +public class Primitive { + public final @NonNull PrimitiveType type; + public final int location; + // The following fields don't make sense for all types. + // Box and Glue have width only. + // Penalty has both width and penalty. + // Word_break has penalty only. + public final float width; + public final float penalty; + + /** + * Use {@code PrimitiveType#getNewPrimitive()} + */ + private Primitive(@NonNull PrimitiveType type, int location, float width, float penalty) { + this.type = type; + this.location = location; + this.width = width; + this.penalty = penalty; + } + + public static enum PrimitiveType { + /** + * Something with a constant width that is to be typeset - like a character. + */ + BOX, + /** + * Blank space with fixed width. + */ + GLUE, + /** + * Aesthetic cost indicating how desirable breaking at this point will be. A penalty of + * {@link #PENALTY_INFINITY} means a forced non-break, whereas a penalty of negative + * {@code #PENALTY_INFINITY} means a forced break. + * <p/> + * Currently, it only stores penalty with values 0 or -infinity. + */ + PENALTY, + /** + * For tabs - variable width space. + */ + VARIABLE, + /** + * Possible breakpoints within a word. Think of this as a high cost {@link #PENALTY}. + */ + WORD_BREAK; + + public Primitive getNewPrimitive(int location) { + assert this == VARIABLE; + return new Primitive(this, location, 0f, 0f); + } + + public Primitive getNewPrimitive(int location, float value) { + assert this == BOX || this == GLUE || this == WORD_BREAK; + if (this == BOX || this == GLUE) { + return new Primitive(this, location, value, 0f); + } else { + return new Primitive(this, location, 0f, value); + } + } + + public Primitive getNewPrimitive(int location, float width, float penalty) { + assert this == PENALTY; + return new Primitive(this, location, width, penalty); + } + + // forced non-break, negative infinity is forced break. + public static final float PENALTY_INFINITY = 1e7f; + } +} + diff --git a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java index 5a467b2..c48b771 100644 --- a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java +++ b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java @@ -1,55 +1,99 @@ package android.text; +import com.android.annotations.NonNull; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; -import java.text.CharacterIterator; -import java.util.Arrays; -import java.util.Locale; +import android.text.StaticLayout.LineBreaks; +import android.text.Primitive.PrimitiveType; + +import java.util.ArrayList; +import java.util.List; -import com.ibm.icu.lang.UCharacter; import com.ibm.icu.text.BreakIterator; import com.ibm.icu.util.ULocale; import javax.swing.text.Segment; /** * Delegate that provides implementation for native methods in {@link android.text.StaticLayout} - * - * Through the layoutlib_create tool, selected methods of Handler have been replaced - * by calls to methods of the same name in this delegate class. - * + * <p/> + * Through the layoutlib_create tool, selected methods of Handler have been replaced by calls to + * methods of the same name in this delegate class. */ public class StaticLayout_Delegate { - /** - * Fills the recycle array with positions that are suitable to break the text at. The array - * must be terminated by '-1'. - */ + private static final char CHAR_SPACE = 0x20; + private static final char CHAR_TAB = 0x09; + private static final char CHAR_NEWLINE = 0x0A; + private static final char CHAR_ZWSP = 0x200B; // Zero width space. + @LayoutlibDelegate - /*package*/ static int[] nLineBreakOpportunities(String locale, char[] text, int length, - int[] recycle) { - BreakIterator iterator = BreakIterator.getLineInstance(new ULocale(locale)); - Segment segment = new Segment(text, 0, length); - iterator.setText(segment); - if (recycle == null) { - // Because 42 is the answer to everything. - recycle = new int[42]; + /*package*/ static int nComputeLineBreaks(String locale, char[] inputText, float[] widths, + int length, float firstWidth, int firstWidthLineCount, float restWidth, + int[] variableTabStops, int defaultTabStop, boolean optimize, LineBreaks recycle, + int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength) { + + // compute all possible breakpoints. + BreakIterator it = BreakIterator.getLineInstance(new ULocale(locale)); + it.setText(new Segment(inputText, 0, length)); + // average word length in english is 5. So, initialize the possible breaks with a guess. + List<Integer> breaks = new ArrayList<Integer>((int) Math.ceil(length / 5d)); + int loc; + it.first(); + while ((loc = it.next()) != BreakIterator.DONE) { + breaks.add(loc); } - int breakOpp = iterator.first(); - recycle[0] = breakOpp; - //noinspection ConstantConditions - assert BreakIterator.DONE == -1; - for (int i = 1; breakOpp != BreakIterator.DONE; ++i) { - if (i >= recycle.length) { - recycle = doubleSize(recycle); - } - assert (i < recycle.length); - breakOpp = iterator.next(); - recycle[i] = breakOpp; + + LineWidth lineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth); + TabStops tabStopCalculator = new TabStops(variableTabStops, defaultTabStop); + List<Primitive> primitives = computePrimitives(inputText, widths, length, breaks); + LineBreaker lineBreaker; + if (optimize) { + lineBreaker = new OptimizingLineBreaker(primitives, lineWidth, tabStopCalculator); + } else { + lineBreaker = new GreedyLineBreaker(primitives, lineWidth, tabStopCalculator); } - return recycle; + lineBreaker.computeBreaks(recycle); + return recycle.breaks.length; } - private static int[] doubleSize(int[] array) { - return Arrays.copyOf(array, array.length * 2); + /** + * Compute metadata each character - things which help in deciding if it's possible to break + * at a point or not. + */ + @NonNull + private static List<Primitive> computePrimitives(@NonNull char[] text, @NonNull float[] widths, + int length, @NonNull List<Integer> breaks) { + // Initialize the list with a guess of the number of primitives: + // 2 Primitives per non-whitespace char and approx 5 chars per word (i.e. 83% chars) + List<Primitive> primitives = new ArrayList<Primitive>(((int) Math.ceil(length * 1.833))); + int breaksSize = breaks.size(); + int breakIndex = 0; + for (int i = 0; i < length; i++) { + char c = text[i]; + if (c == CHAR_SPACE || c == CHAR_ZWSP) { + primitives.add(PrimitiveType.GLUE.getNewPrimitive(i, widths[i])); + } else if (c == CHAR_TAB) { + primitives.add(PrimitiveType.VARIABLE.getNewPrimitive(i)); + } else if (c != CHAR_NEWLINE) { + while (breakIndex < breaksSize && breaks.get(breakIndex) < i) { + breakIndex++; + } + Primitive p; + if (widths[i] != 0) { + if (breakIndex < breaksSize && breaks.get(breakIndex) == i) { + p = PrimitiveType.PENALTY.getNewPrimitive(i, 0, 0); + } else { + p = PrimitiveType.WORD_BREAK.getNewPrimitive(i, 0); + } + primitives.add(p); + } + + primitives.add(PrimitiveType.BOX.getNewPrimitive(i, widths[i])); + } + } + // final break at end of everything + primitives.add( + PrimitiveType.PENALTY.getNewPrimitive(length, 0, -PrimitiveType.PENALTY_INFINITY)); + return primitives; } } diff --git a/tools/layoutlib/bridge/src/android/text/TabStops.java b/tools/layoutlib/bridge/src/android/text/TabStops.java new file mode 100644 index 0000000..cff6b93 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/text/TabStops.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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 android.text; + +import com.android.annotations.Nullable; + +// Based on the native implementation of TabStops in +// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260 +public class TabStops { + @Nullable + private int[] mStops; + private final int mTabWidth; + + public TabStops(@Nullable int[] stops, int defaultTabWidth) { + mTabWidth = defaultTabWidth; + mStops = stops; + } + + public float width(float widthSoFar) { + if (mStops != null) { + for (int i : mStops) { + if (i > widthSoFar) { + return i; + } + } + } + // find the next tabStop after widthSoFar. + return (int) ((widthSoFar + mTabWidth) / mTabWidth) * mTabWidth; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java index e0f87fd..feb2590 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java @@ -86,11 +86,14 @@ public class BridgeRenderSession extends RenderSession { } @Override - public Result render(long timeout) { + public Result render(long timeout, boolean forceMeasure) { try { Bridge.prepareThread(); mLastResult = mSession.acquire(timeout); if (mLastResult.isSuccess()) { + if (forceMeasure) { + mSession.invalidateRenderingSize(); + } mLastResult = mSession.render(false /*freshRender*/); } } finally { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index a2eed9a..20eddd6 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -1571,7 +1571,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { return null; } - private void invalidateRenderingSize() { + public void invalidateRenderingSize() { mMeasuredScreenWidth = mMeasuredScreenHeight = -1; } diff --git a/tools/obbtool/Android.mk b/tools/obbtool/Android.mk index 9ff56d6..99a5671 100644 --- a/tools/obbtool/Android.mk +++ b/tools/obbtool/Android.mk @@ -13,7 +13,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ Main.cpp -LOCAL_CFLAGS := -Wall -Werror +LOCAL_CFLAGS := -Wall -Werror -Wno-mismatched-tags #LOCAL_C_INCLUDES += @@ -36,7 +36,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := pbkdf2gen LOCAL_MODULE_TAGS := optional -LOCAL_CFLAGS := -Wall -Werror +LOCAL_CFLAGS := -Wall -Werror -Wno-mismatched-tags LOCAL_SRC_FILES := pbkdf2gen.cpp LOCAL_LDLIBS += -ldl LOCAL_C_INCLUDES := external/openssl/include $(LOCAL_C_INCLUDES) |