From b060582edaf25eb866d15e8df5b7a85cf586f9e9 Mon Sep 17 00:00:00 2001 From: Sriram Ramasubramanian Date: Thu, 15 Dec 2016 22:23:15 -0800 Subject: [PATCH] Initial commit fbshipit-source-id: 4799a0dd91a4b569e932c08ecc5849b2a8943dbb --- .gitignore | 8 + CONTRIBUTING.md | 59 + LICENSE | 30 + PATENTS | 33 + README.md | 92 ++ build.gradle | 26 + docs/api.html | 115 ++ docs/favicon.png | Bin 0 -> 2303 bytes docs/index.html | 248 ++++ docs/javadoc/allclasses-frame.html | 24 + docs/javadoc/allclasses-noframe.html | 24 + .../fbui/textlayoutbuilder/GlyphWarmer.html | 233 +++ .../ResourceTextLayoutHelper.html | 370 +++++ .../TextLayoutBuilder.MeasureMode.html | 169 +++ .../textlayoutbuilder/TextLayoutBuilder.html | 1255 +++++++++++++++++ .../glyphwarmer/GlyphWarmerImpl.html | 287 ++++ .../glyphwarmer/package-frame.html | 20 + .../glyphwarmer/package-summary.html | 156 ++ .../glyphwarmer/package-tree.html | 135 ++ .../fbui/textlayoutbuilder/package-frame.html | 29 + .../textlayoutbuilder/package-summary.html | 196 +++ .../fbui/textlayoutbuilder/package-tree.html | 144 ++ .../util/LayoutMeasureUtil.html | 305 ++++ .../textlayoutbuilder/util/package-frame.html | 20 + .../util/package-summary.html | 151 ++ .../textlayoutbuilder/util/package-tree.html | 135 ++ docs/javadoc/constant-values.html | 172 +++ docs/javadoc/deprecated-list.html | 122 ++ docs/javadoc/help-doc.html | 223 +++ docs/javadoc/index-all.html | 431 ++++++ docs/javadoc/index.html | 74 + docs/javadoc/overview-frame.html | 23 + docs/javadoc/overview-summary.html | 150 ++ docs/javadoc/overview-tree.html | 148 ++ docs/javadoc/package-list | 3 + docs/javadoc/script.js | 30 + docs/javadoc/stylesheet.css | 571 ++++++++ docs/logo.png | Bin 0 -> 3589 bytes docs/main.css | 335 +++++ gradle.properties | 7 + gradle/android-maven-install.gradle | 37 + gradle/android-tasks.gradle | 47 + gradle/bintray.gradle | 57 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 52818 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 169 +++ gradlew.bat | 84 ++ library/.gitignore | 8 + library/build.gradle | 27 + .../libs/android-support-v4-text/.gitignore | 8 + .../libs/android-support-v4-text/build.gradle | 10 + .../v4/text/TextDirectionHeuristicCompat.java | 29 + .../text/TextDirectionHeuristicsCompat.java | 35 + library/libs/android-text/.gitignore | 8 + library/libs/android-text/build.gradle | 10 + .../src/main/java/android/text/Layout.java | 36 + .../main/java/android/text/StaticLayout.java | 37 + .../android/text/TextDirectionHeuristic.java | 29 + .../android/text/TextDirectionHeuristics.java | 35 + .../src/main/java/android/text/TextPaint.java | 29 + .../src/main/java/android/text/TextUtils.java | 36 + library/libs/proxy/.gitignore | 8 + library/libs/proxy/build.gradle | 32 + library/libs/proxy/gradle.properties | 7 + .../proxy/StaticLayoutProxy.java | 72 + library/src/javadoc/stylesheet.css | 571 ++++++++ library/src/main/AndroidManifest.xml | 11 + .../fbui/textlayoutbuilder/GlyphWarmer.java | 26 + .../ResourceTextLayoutHelper.java | 214 +++ .../textlayoutbuilder/StaticLayoutHelper.java | 301 ++++ .../textlayoutbuilder/TextLayoutBuilder.java | 810 +++++++++++ .../glyphwarmer/GlyphWarmerImpl.java | 92 ++ .../glyphwarmer/package-info.java | 15 + .../fbui/textlayoutbuilder/package-info.java | 18 + .../util/LayoutMeasureUtil.java | 72 + .../textlayoutbuilder/util/package-info.java | 14 + library/src/main/res/values/attrs.xml | 43 + .../TextLayoutBuilderTest.java | 329 +++++ .../TextMeasureModeTest.java | 90 ++ .../glyphwarmer/GlyphWarmerImplTest.java | 62 + .../shadows/ShadowLayout.java | 35 + .../shadows/ShadowPicture.java | 40 + .../util/LayoutMeasureUtilTest.java | 106 ++ proguard-android.txt | 7 + release-sonatype.gradle | 92 ++ release.gradle | 5 + settings.gradle | 4 + 87 files changed, 10366 insertions(+) create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 PATENTS create mode 100644 README.md create mode 100644 build.gradle create mode 100644 docs/api.html create mode 100644 docs/favicon.png create mode 100644 docs/index.html create mode 100644 docs/javadoc/allclasses-frame.html create mode 100644 docs/javadoc/allclasses-noframe.html create mode 100644 docs/javadoc/com/facebook/fbui/textlayoutbuilder/GlyphWarmer.html create mode 100644 docs/javadoc/com/facebook/fbui/textlayoutbuilder/ResourceTextLayoutHelper.html create mode 100644 docs/javadoc/com/facebook/fbui/textlayoutbuilder/TextLayoutBuilder.MeasureMode.html create mode 100644 docs/javadoc/com/facebook/fbui/textlayoutbuilder/TextLayoutBuilder.html create mode 100644 docs/javadoc/com/facebook/fbui/textlayoutbuilder/glyphwarmer/GlyphWarmerImpl.html create mode 100644 docs/javadoc/com/facebook/fbui/textlayoutbuilder/glyphwarmer/package-frame.html create mode 100644 docs/javadoc/com/facebook/fbui/textlayoutbuilder/glyphwarmer/package-summary.html create mode 100644 docs/javadoc/com/facebook/fbui/textlayoutbuilder/glyphwarmer/package-tree.html create mode 100644 docs/javadoc/com/facebook/fbui/textlayoutbuilder/package-frame.html create mode 100644 docs/javadoc/com/facebook/fbui/textlayoutbuilder/package-summary.html create mode 100644 docs/javadoc/com/facebook/fbui/textlayoutbuilder/package-tree.html create mode 100644 docs/javadoc/com/facebook/fbui/textlayoutbuilder/util/LayoutMeasureUtil.html create mode 100644 docs/javadoc/com/facebook/fbui/textlayoutbuilder/util/package-frame.html create mode 100644 docs/javadoc/com/facebook/fbui/textlayoutbuilder/util/package-summary.html create mode 100644 docs/javadoc/com/facebook/fbui/textlayoutbuilder/util/package-tree.html create mode 100644 docs/javadoc/constant-values.html create mode 100644 docs/javadoc/deprecated-list.html create mode 100644 docs/javadoc/help-doc.html create mode 100644 docs/javadoc/index-all.html create mode 100644 docs/javadoc/index.html create mode 100644 docs/javadoc/overview-frame.html create mode 100644 docs/javadoc/overview-summary.html create mode 100644 docs/javadoc/overview-tree.html create mode 100644 docs/javadoc/package-list create mode 100644 docs/javadoc/script.js create mode 100644 docs/javadoc/stylesheet.css create mode 100644 docs/logo.png create mode 100644 docs/main.css create mode 100644 gradle.properties create mode 100644 gradle/android-maven-install.gradle create mode 100644 gradle/android-tasks.gradle create mode 100644 gradle/bintray.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 library/.gitignore create mode 100644 library/build.gradle create mode 100644 library/libs/android-support-v4-text/.gitignore create mode 100644 library/libs/android-support-v4-text/build.gradle create mode 100644 library/libs/android-support-v4-text/src/main/java/android/support/v4/text/TextDirectionHeuristicCompat.java create mode 100644 library/libs/android-support-v4-text/src/main/java/android/support/v4/text/TextDirectionHeuristicsCompat.java create mode 100644 library/libs/android-text/.gitignore create mode 100644 library/libs/android-text/build.gradle create mode 100644 library/libs/android-text/src/main/java/android/text/Layout.java create mode 100644 library/libs/android-text/src/main/java/android/text/StaticLayout.java create mode 100644 library/libs/android-text/src/main/java/android/text/TextDirectionHeuristic.java create mode 100644 library/libs/android-text/src/main/java/android/text/TextDirectionHeuristics.java create mode 100644 library/libs/android-text/src/main/java/android/text/TextPaint.java create mode 100644 library/libs/android-text/src/main/java/android/text/TextUtils.java create mode 100644 library/libs/proxy/.gitignore create mode 100644 library/libs/proxy/build.gradle create mode 100644 library/libs/proxy/gradle.properties create mode 100644 library/libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy/StaticLayoutProxy.java create mode 100644 library/src/javadoc/stylesheet.css create mode 100644 library/src/main/AndroidManifest.xml create mode 100644 library/src/main/java/com/facebook/fbui/textlayoutbuilder/GlyphWarmer.java create mode 100644 library/src/main/java/com/facebook/fbui/textlayoutbuilder/ResourceTextLayoutHelper.java create mode 100644 library/src/main/java/com/facebook/fbui/textlayoutbuilder/StaticLayoutHelper.java create mode 100644 library/src/main/java/com/facebook/fbui/textlayoutbuilder/TextLayoutBuilder.java create mode 100644 library/src/main/java/com/facebook/fbui/textlayoutbuilder/glyphwarmer/GlyphWarmerImpl.java create mode 100644 library/src/main/java/com/facebook/fbui/textlayoutbuilder/glyphwarmer/package-info.java create mode 100644 library/src/main/java/com/facebook/fbui/textlayoutbuilder/package-info.java create mode 100644 library/src/main/java/com/facebook/fbui/textlayoutbuilder/util/LayoutMeasureUtil.java create mode 100644 library/src/main/java/com/facebook/fbui/textlayoutbuilder/util/package-info.java create mode 100644 library/src/main/res/values/attrs.xml create mode 100644 library/src/test/java/com/facebook/fbui/textlayoutbuilder/TextLayoutBuilderTest.java create mode 100644 library/src/test/java/com/facebook/fbui/textlayoutbuilder/TextMeasureModeTest.java create mode 100644 library/src/test/java/com/facebook/fbui/textlayoutbuilder/glyphwarmer/GlyphWarmerImplTest.java create mode 100644 library/src/test/java/com/facebook/fbui/textlayoutbuilder/shadows/ShadowLayout.java create mode 100644 library/src/test/java/com/facebook/fbui/textlayoutbuilder/shadows/ShadowPicture.java create mode 100644 library/src/test/java/com/facebook/fbui/textlayoutbuilder/util/LayoutMeasureUtilTest.java create mode 100644 proguard-android.txt create mode 100644 release-sonatype.gradle create mode 100644 release.gradle create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cba8ae6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.gradle +.DS_Store +.idea +build/ +local.properties +localhost/ +obj/ +*.iml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..45a0ba9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# Contributing to TextLayoutBuilder +We want to make contributing to this project as easy and transparent as +possible. + + +## Issues +We use GitHub issues to track public bugs. + +When you report an issue the more information the better. Here are some things that will help you get an answer faster: + +- A *title* as well as a body for the issue. +- A screenshot or video of the problem. +- Logcat output, if your app is crashing. +- A snippet of the code in question +- Place code in blocks so that it reads like code: + +``` +```java (or xml) +your code here +```(terminating backticks) +``` + +#### Security bugs + +Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe +disclosure of security bugs. In those cases, please go through the process +outlined on that page and do not file a public issue. + +## Pull Requests +We actively welcome your pull requests. + +1. Fork the repo and create your branch from `master`. +2. If you've added code that should be tested, add tests +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. If you haven't already, complete the Contributor License Agreement ("CLA"). + +## Contributor License Agreement ("CLA") +In order to accept your pull request, we need you to submit a CLA. You only need +to do this once to work on any of Facebook's open source projects. + +Complete your CLA here: . + +## Our Development Process +Each pull request is first submitted into Facebook's internal repositories by a +Facebook team member. Once the commit has successfully passed Facebook's internal +test suite, it will be exported back out from Facebook's repository. We endeavour +to do this as soon as possible for all commits. + +## Coding Style +* 2 spaces for indentation rather than tabs +* 100 character line length +* Although officially archived, we still follow the practice of Oracle's +[Coding Conventions for the Java Programming Language](http://www.oracle.com/technetwork/java/javase/documentation/codeconvtoc-136057.html). + +## License +By contributing to TextLayoutBuilder, you agree that your contributions will be licensed +under its BSD license. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2b70c95 --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ +BSD License + +For TextLayoutBuilder software + +Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/PATENTS b/PATENTS new file mode 100644 index 0000000..14ccaca --- /dev/null +++ b/PATENTS @@ -0,0 +1,33 @@ +Additional Grant of Patent Rights Version 2 + +"Software" means the TextLayoutBuilder software contributed by Facebook, Inc. + +Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software +("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable +(subject to the termination provision below) license under any Necessary +Claims, to make, have made, use, sell, offer to sell, import, and otherwise +transfer the Software. For avoidance of doubt, no license is granted under +Facebook’s rights in any patent claims that are infringed by (i) modifications +to the Software made by you or any third party or (ii) the Software in +combination with any software or other technology. + +The license granted hereunder will terminate, automatically and without notice, +if you (or any of your subsidiaries, corporate affiliates or agents) initiate +directly or indirectly, or take a direct financial interest in, any Patent +Assertion: (i) against Facebook or any of its subsidiaries or corporate +affiliates, (ii) against any party if such Patent Assertion arises in whole or +in part from any software, technology, product or service of Facebook or any of +its subsidiaries or corporate affiliates, or (iii) against any party relating +to the Software. Notwithstanding the foregoing, if Facebook or any of its +subsidiaries or corporate affiliates files a lawsuit alleging patent +infringement against you in the first instance, and you respond by filing a +patent infringement counterclaim in that lawsuit against that party that is +unrelated to the Software, the license granted hereunder will not terminate +under section (i) of this paragraph due to such counterclaim. + +A "Necessary Claim" is a claim of a patent owned by Facebook that is +necessarily infringed by the Software standing alone. + +A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, +or contributory infringement or inducement to infringe any patent, including a +cross-claim or counterclaim. diff --git a/README.md b/README.md new file mode 100644 index 0000000..14037c6 --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +TextLayoutBuilder +================= +Build text [Layout](https://developer.android.com/reference/android/text/Layout.html)s easily on Android. + +![TextLayoutBuilder logo](./docs/logo.png) + +Features +-------- +- Create text `Layout`s easily. +- Reuse builders to create similarly styled `Layout`s. +- Cache `Layout`s of commonly used strings. +- Improve performance using glyph warming. + +Download +-------- +If using Gradle, add this to your `build.gradle`: + +```groovy +compile 'com.facebook.fbui.textlayoutbuilder:textlayoutbuilder:1.0.0' +``` + +or, if using Maven: + +```xml + + com.facebook.fbui.textlayoutbuilder + textlayoutbuilder + 1.0.0 + aar + +``` + +Usage +----- +1. Set the properties on the `TextLayoutBuilder`: + ```java + TextLayoutBuilder builder = new TextLayoutBuilder() + .setTextAppearance(context, resId) + .setText("TextLayoutBuilder makes life easy") + .setWidth(400); + ``` + +2. Call `build()` on the builder to get a `Layout`: + ```java + Layout layout = builder.build(); + ``` + +3. Use the `Layout` in your code: + ```java + public class CustomView extends View { + private Layout mLayout; + + public CustomView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setLayout(Layout layout) { + mLayout = layout; + } + + @Override + protected void onDraw(Canvas canvas) { + super.draw(canvas); + + // Draw the layout. + mLayout.draw(canvas); + } + } + ``` + +Additional Usage +---------------- +1. Cache the layouts for commonly used strings by turning on caching in the `TextLayoutBuilder`. + ```java + mTextLayoutBuilder.setShouldCacheLayout(true); + ``` + +2. Glyph warming provides significant performance boost for large blurbs of text. +Turn this on and pass in a `GlyphWarmer` for the `TextLayoutBuilder`. + ```java + mTextLayoutBuilder + .setShouldWarmText(true) + .setGlyphWarmer(new GlyphWarmerImpl()); + ``` + +3. Import a style defined in XML into a `TextLayoutBuilder` object. + ```java + ResourceTextLayoutHelper.updateFromStyleResource( + mTextLayoutBuilder, // builder object + mContext, // Activity context + resId); // style resource id + ``` diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..187adf0 --- /dev/null +++ b/build.gradle @@ -0,0 +1,26 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.1.3' + } +} + +allprojects { + repositories { + jcenter() + } +} + +ext { + minSdkVersion = 9 + targetSdkVersion = 24 + compileSdkVersion = 24 + buildToolsVersion = '24.0.2' + + junitVersion = '4.12' + mockitoCoreVersion = '1.10.19' + robolectricVersion = '3.0' + supportLibVersion = '24.2.1' +} diff --git a/docs/api.html b/docs/api.html new file mode 100644 index 0000000..b55aa83 --- /dev/null +++ b/docs/api.html @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + TextLayoutBuilder - API + + + + + + + + +
+
+ + +

TextLayoutBuilder

+
+ +
+ + +
+
+ + +
+ +
+ + + + + + + + diff --git a/docs/favicon.png b/docs/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..002a3ef068b923429a0f9dac2ce9257850503ec3 GIT binary patch literal 2303 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 literal 0 HcmV?d00001 diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..73834d3 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + TextLayoutBuilder + + + + + + + +
+
+ + +

TextLayoutBuilder

+
+ +
+ + +
+
+ + +
+
+
+

Build text layouts easily on Android!

+
+
+

TextLayoutBuilder uses a builder pattern to configure the properties + required to create a Layout. The methods on this builder class are + similar to TextView's. On calling build(), the + TextLayoutBuilder creates a text Layout based on + the properties set on it.

+
    +
  • Create text layouts easily.
  • +
  • Reuse builders to create similarly styled layouts.
  • +
  • Cache layouts of commonly used strings.
  • +
  • Improve performance with glyph warming.
  • +
+
+
+
+Layout layout = new TextLayoutBuilder()
+    .setTextAppearance(context, resId)
+    .setText("TextLayoutBuilder makes life easy")
+    .setWidth(400)
+    .build();
+
+
+
+
+ +
+
+
+ + +
+
+
+

Features

+
+
+

Builder

+

TextLayoutBuilder uses a builder pattern to configure the properties + for the Layout. Gone are those days of trying to set properties on + a StaticLayout.

+
+
+

Caching

+

Same properties will return same Layout on subsequent build() + calls. This reduces allocation on commonly used texts.

+
+
+

Glyph Warming

+

On 4.0+ devices, Android uses a texture cache to warm up the glyphs. By drawing these + glyphs on a background thread onto a Picture, TextLayoutBuilder warms + these glyphs and can help them render at 0ms.

+
+
+
+
+
+ + +
+
+
+

Download

+
    +
  • + If using Gradle, add this to your build.gradle: +
    compile 'com.facebook.fbui.textlayoutbuilder:textlayoutbuilder:1.0.0'
    +
  • +
  • + or, if using Maven: +
    +<dependency>
    +    <groupId>com.facebook.fbui.textlayoutbuilder</groupId>
    +    <artifactId>textlayoutbuilder</artifactId>
    +    <version>1.0.0</version>
    +    <typen>aar</type>
    +</dependency>
    +
  • +
+
+
+
+ + +
+
+
+

Usage

+
    +
  1. + Set the properties on the TextLayoutBuilder: +
    +TextLayoutBuilder builder = new TextLayoutBuilder()
    +    .setTextAppearance(context, resId)
    +    .setText("TextLayoutBuilder makes life easy")
    +    .setWidth(400);
    +
  2. +
  3. + Call build() on the builder to get a Layout: +
    Layout layout = builder.build();
    +
  4. +
  5. + Use the Layout in your code: +
    layout.draw(canvas);
    +
  6. +
+
+
+
+ + +
+
+
+

Additional Usage

+
    +
  1. + Cache the layouts for commonly used strings by turning on caching in the TextLayoutBuilder. +
    mTextLayoutBuilder.setShouldCacheLayout(true);
    +
  2. +
  3. + Glyph warming provides significant performance boost for large blurbs of text. + Turn this on and pass in a GlyphWarmer for the TextLayoutBuilder. +
    +mTextLayoutBuilder
    +    .setShouldWarmText(true)
    +    .setGlyphWarmer(new GlyphWarmerImpl());
    +
  4. +
  5. + Import a style defined in XML into a TextLayoutBuilder object. +
    +ResourceTextLayoutHelper.updateFromStyleResource(
    +    mTextLayoutBuilder,
    +    mContext,
    +    resId);
    +
  6. +
+
+
+
+ + + + + + + + diff --git a/docs/javadoc/allclasses-frame.html b/docs/javadoc/allclasses-frame.html new file mode 100644 index 0000000..29fefa9 --- /dev/null +++ b/docs/javadoc/allclasses-frame.html @@ -0,0 +1,24 @@ + + + + + +All Classes (TextLayoutBuilder API) + + + + + +

All Classes

+ + + diff --git a/docs/javadoc/allclasses-noframe.html b/docs/javadoc/allclasses-noframe.html new file mode 100644 index 0000000..0efdee0 --- /dev/null +++ b/docs/javadoc/allclasses-noframe.html @@ -0,0 +1,24 @@ + + + + + +All Classes (TextLayoutBuilder API) + + + + + +

All Classes

+ + + diff --git a/docs/javadoc/com/facebook/fbui/textlayoutbuilder/GlyphWarmer.html b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/GlyphWarmer.html new file mode 100644 index 0000000..eda3755 --- /dev/null +++ b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/GlyphWarmer.html @@ -0,0 +1,233 @@ + + + + + +GlyphWarmer (TextLayoutBuilder API) + + + + + + + + + + + + +
+
com.facebook.fbui.textlayoutbuilder
+

Interface GlyphWarmer

+
+
+
+
    +
  • +
    +
    All Known Implementing Classes:
    +
    GlyphWarmerImpl
    +
    +
    +
    +
    public interface GlyphWarmer
    +
    Specifies an interface that a class has to implement + to warm the text Layout in the background. + This approach helps in drawing text in post Android 4.0 devices.
    +
  • +
+
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        warmLayout

        +
        void warmLayout(Layout layout)
        +
        Warms the text layout.
        +
        +
        Parameters:
        +
        layout - The layout
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/docs/javadoc/com/facebook/fbui/textlayoutbuilder/ResourceTextLayoutHelper.html b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/ResourceTextLayoutHelper.html new file mode 100644 index 0000000..885229d --- /dev/null +++ b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/ResourceTextLayoutHelper.html @@ -0,0 +1,370 @@ + + + + + +ResourceTextLayoutHelper (TextLayoutBuilder API) + + + + + + + + + + + + +
+
com.facebook.fbui.textlayoutbuilder
+

Class ResourceTextLayoutHelper

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • com.facebook.fbui.textlayoutbuilder.ResourceTextLayoutHelper
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class ResourceTextLayoutHelper
    +extends Object
    +
    An utility class to update a TextLayoutBuilder from an Android resource.
    +
  • +
+
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        ResourceTextLayoutHelper

        +
        public ResourceTextLayoutHelper()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        updateFromStyleResource

        +
        public static void updateFromStyleResource(TextLayoutBuilder builder,
        +                                           Context context,
        +                                           int styleRes)
        +
        Sets the values for a TextLayoutBuilder from a style resource.
        +
        +
        Parameters:
        +
        builder - The TextLayoutBuilder
        +
        context - The Context to use for resolving the attributes
        +
        styleRes - The style resource identifier
        +
        +
      • +
      + + + +
        +
      • +

        updateFromStyleResource

        +
        public static void updateFromStyleResource(TextLayoutBuilder builder,
        +                                           Context context,
        +                                           int styleAttr,
        +                                           int styleRes)
        +
        Sets the values for a TextLayoutBuilder from a style resource or a themed attribute.
        +
        +
        Parameters:
        +
        builder - The TextLayoutBuilder
        +
        context - The Context to use for resolving the attributes
        +
        styleAttr - The themed style attribute
        +
        styleRes - The style resource identifier
        +
        +
      • +
      + + + +
        +
      • +

        updateFromStyleResource

        +
        public static void updateFromStyleResource(TextLayoutBuilder builder,
        +                                           Context context,
        +                                           AttributeSet attrs,
        +                                           int styleAttr,
        +                                           int styleRes)
        +
        Sets the values for a TextLayoutBuilder from a style resource or a themed attribute.
        +
        +
        Parameters:
        +
        builder - The TextLayoutBuilder
        +
        context - The Context to use for resolving the attributes
        +
        attrs - The AttributeSet used during inflation
        +
        styleAttr - The themed style attribute
        +
        styleRes - The style resource identifier
        +
        +
      • +
      + + + +
        +
      • +

        setTextAppearance

        +
        public static void setTextAppearance(TextLayoutBuilder builder,
        +                                     Context context,
        +                                     int resId)
        +
        Sets a text appearance for the layout.
        +
        +
        Parameters:
        +
        builder - The TextLayoutBuilder instance
        +
        context - The Context to use for resolving attributes
        +
        resId - The resource identifier of the text appearance
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/docs/javadoc/com/facebook/fbui/textlayoutbuilder/TextLayoutBuilder.MeasureMode.html b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/TextLayoutBuilder.MeasureMode.html new file mode 100644 index 0000000..3da4700 --- /dev/null +++ b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/TextLayoutBuilder.MeasureMode.html @@ -0,0 +1,169 @@ + + + + + +TextLayoutBuilder.MeasureMode (TextLayoutBuilder API) + + + + + + + + + + + + +
+
com.facebook.fbui.textlayoutbuilder
+

Annotation Type TextLayoutBuilder.MeasureMode

+
+
+
+ +
+
+ + + + + + + diff --git a/docs/javadoc/com/facebook/fbui/textlayoutbuilder/TextLayoutBuilder.html b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/TextLayoutBuilder.html new file mode 100644 index 0000000..f1f8e5f --- /dev/null +++ b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/TextLayoutBuilder.html @@ -0,0 +1,1255 @@ + + + + + +TextLayoutBuilder (TextLayoutBuilder API) + + + + + + + + + + + + +
+
com.facebook.fbui.textlayoutbuilder
+

Class TextLayoutBuilder

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class TextLayoutBuilder
    +extends Object
    +
    An utility class to create text Layouts easily. +

    + This class uses a Builder pattern to allow re-using the same object + to create text Layouts with similar properties.

    +
  • +
+
+
+ +
+
+
    +
  • + + + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        TextLayoutBuilder

        +
        public TextLayoutBuilder()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + + + + + +
        +
      • +

        setWidth

        +
        public TextLayoutBuilder setWidth(int width,
        +                                  int measureMode)
        +
        Sets the intended width of the text layout while respecting the measure mode.
        +
        +
        Parameters:
        +
        width - The width of the text layout
        +
        measureMode - The mode with which to treat the given width
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        See Also:
        +
        setWidth(int)
        +
        +
      • +
      + + + +
        +
      • +

        getText

        +
        public CharSequence getText()
        +
        Returns the text that would be packed in a layout by this TextLayoutBuilder.
        +
        +
        Returns:
        +
        The text used by this TextLayoutBuilder
        +
        +
      • +
      + + + + + + + +
        +
      • +

        getTextSize

        +
        public float getTextSize()
        +
        Returns the text size for this TextLayoutBuilder.
        +
        +
        Returns:
        +
        The text size used by this TextLayoutBuilder
        +
        +
      • +
      + + + +
        +
      • +

        setTextSize

        +
        public TextLayoutBuilder setTextSize(int size)
        +
        Sets the text size for the layout.
        +
        +
        Parameters:
        +
        size - The text size in pixels
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        +
      • +
      + + + +
        +
      • +

        getTextColor

        +
        public int getTextColor()
        +
        Returns the text color for this TextLayoutBuilder.
        +
        +
        Returns:
        +
        The text color used by this TextLayoutBuilder
        +
        +
      • +
      + + + +
        +
      • +

        setTextColor

        +
        public TextLayoutBuilder setTextColor(int color)
        +
        Sets the text color for the layout.
        +
        +
        Parameters:
        +
        color - The text color for the layout
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        +
      • +
      + + + +
        +
      • +

        setTextColor

        +
        public TextLayoutBuilder setTextColor(ColorStateList colorStateList)
        +
        Sets the text color for the layout.
        +
        +
        Parameters:
        +
        colorStateList - The text color state list for the layout
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        +
      • +
      + + + +
        +
      • +

        getLinkColor

        +
        public int getLinkColor()
        +
        Returns the link color for this TextLayoutBuilder.
        +
        +
        Returns:
        +
        The link color used by this TextLayoutBuilder
        +
        +
      • +
      + + + +
        +
      • +

        setLinkColor

        +
        public TextLayoutBuilder setLinkColor(int linkColor)
        +
        Sets the link color for the text in the layout.
        +
        +
        Parameters:
        +
        linkColor - The link color
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        +
      • +
      + + + +
        +
      • +

        getTextSpacingExtra

        +
        public float getTextSpacingExtra()
        +
        Returns the text spacing extra for this TextLayoutBuilder.
        +
        +
        Returns:
        +
        The text spacing extra used by this TextLayoutBuilder
        +
        +
      • +
      + + + +
        +
      • +

        setTextSpacingExtra

        +
        public TextLayoutBuilder setTextSpacingExtra(float spacingExtra)
        +
        Sets the text extra spacing for the layout.
        +
        +
        Parameters:
        +
        spacingExtra - the extra space that is added to the height of each line
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        +
      • +
      + + + +
        +
      • +

        getTextSpacingMultiplier

        +
        public float getTextSpacingMultiplier()
        +
        Returns the text spacing multiplier for this TextLayoutBuilder.
        +
        +
        Returns:
        +
        The text spacing multiplier used by this TextLayoutBuilder
        +
        +
      • +
      + + + +
        +
      • +

        setTextSpacingMultiplier

        +
        public TextLayoutBuilder setTextSpacingMultiplier(float spacingMultiplier)
        +
        Sets the line spacing multiplier for the layout.
        +
        +
        Parameters:
        +
        spacingMultiplier - the value by which each line's height is multiplied
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        +
      • +
      + + + +
        +
      • +

        getIncludeFontPadding

        +
        public boolean getIncludeFontPadding()
        +
        Returns whether this TextLayoutBuilder should include font padding.
        +
        +
        Returns:
        +
        Whether this TextLayoutBuilder should include font padding
        +
        +
      • +
      + + + +
        +
      • +

        setIncludeFontPadding

        +
        public TextLayoutBuilder setIncludeFontPadding(boolean shouldInclude)
        +
        Set whether the text Layout includes extra top and bottom padding to make + room for accents that go above the normal ascent and descent. +

        + The default is true.

        +
        +
        Parameters:
        +
        shouldInclude - Whether to include font padding or not
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        +
      • +
      + + + +
        +
      • +

        getAlignment

        +
        public Layout.Alignment getAlignment()
        +
        Returns the text alignment for this TextLayoutBuilder.
        +
        +
        Returns:
        +
        The text alignment used by this TextLayoutBuilder
        +
        +
      • +
      + + + + + + + +
        +
      • +

        getTextDirection

        +
        public TextDirectionHeuristicCompat getTextDirection()
        +
        Returns the text direction for this TextLayoutBuilder.
        +
        +
        Returns:
        +
        The text direction used by this TextLayoutBuilder
        +
        +
      • +
      + + + +
        +
      • +

        setTextDirection

        +
        public TextLayoutBuilder setTextDirection(TextDirectionHeuristicCompat textDirection)
        +
        Sets the text direction heuristic for the layout. +

        + TextDirectionHeuristicCompat describes how to evaluate the text + of this Layout to know whether to use RTL or LTR text direction

        +
        +
        Parameters:
        +
        textDirection - The text direction heuristic for the layout
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        +
      • +
      + + + +
        +
      • +

        setShadowLayer

        +
        public TextLayoutBuilder setShadowLayer(float radius,
        +                                        float dx,
        +                                        float dy,
        +                                        int color)
        +
        Sets the shadow layer for the layout.
        +
        +
        Parameters:
        +
        radius - The radius of the blur for shadow
        +
        dx - The horizontal translation of the origin
        +
        dy - The vertical translation of the origin
        +
        color - The shadow color
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        +
      • +
      + + + +
        +
      • +

        setTextStyle

        +
        public TextLayoutBuilder setTextStyle(int style)
        +
        Sets a text style for the layout.
        +
        +
        Parameters:
        +
        style - The text style for the layout
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        +
      • +
      + + + +
        +
      • +

        getTypeface

        +
        public Typeface getTypeface()
        +
        Returns the typeface for this TextLayoutBuilder.
        +
        +
        Returns:
        +
        The typeface used by this TextLayoutBuilder
        +
        +
      • +
      + + + +
        +
      • +

        setTypeface

        +
        public TextLayoutBuilder setTypeface(Typeface typeface)
        +
        Sets the typeface used by this TextLayoutBuilder.
        +
        +
        Parameters:
        +
        typeface - The typeface for this TextLayoutBuilder
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        +
      • +
      + + + +
        +
      • +

        getDrawableState

        +
        public int[] getDrawableState()
        +
        Returns the drawable state for this TextLayoutBuilder.
        +
        +
        Returns:
        +
        The drawable state used by this TextLayoutBuilder
        +
        +
      • +
      + + + +
        +
      • +

        setDrawableState

        +
        public TextLayoutBuilder setDrawableState(int[] drawableState)
        +
        Updates the text colors based on the drawable state.
        +
        +
        Parameters:
        +
        drawableState - The current drawable state of the View holding this layout
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        +
      • +
      + + + +
        +
      • +

        getEllipsize

        +
        public TextUtils.TruncateAt getEllipsize()
        +
        Returns the text ellipsize for this TextLayoutBuilder.
        +
        +
        Returns:
        +
        The text ellipsize used by this TextLayoutBuilder
        +
        +
      • +
      + + + + + + + +
        +
      • +

        getSingleLine

        +
        public boolean getSingleLine()
        +
        Returns whether the TextLayoutBuilder should show a single line.
        +
        +
        Returns:
        +
        Whether the TextLayoutBuilder should show a single line or not
        +
        +
      • +
      + + + +
        +
      • +

        setSingleLine

        +
        public TextLayoutBuilder setSingleLine(boolean singleLine)
        +
        Sets whether the text should be in a single line or not.
        +
        +
        Parameters:
        +
        singleLine - Whether the text should be in a single line or not
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        See Also:
        +
        setMaxLines(int)
        +
        +
      • +
      + + + +
        +
      • +

        getMaxLines

        +
        public int getMaxLines()
        +
        Returns the number of max lines used by this TextLayoutBuilder.
        +
        +
        Returns:
        +
        The number of max lines for this TextLayoutBuilder
        +
        +
      • +
      + + + +
        +
      • +

        setMaxLines

        +
        public TextLayoutBuilder setMaxLines(int maxLines)
        +
        Sets a maximum number of lines to be shown by the Layout. +

        + Note: Gingerbread always default to two lines max when ellipsized. This cannot be changed. + Use a TextView if you want more control over the number of lines.

        +
        +
        Parameters:
        +
        maxLines - The number of maxLines to show in this Layout
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        See Also:
        +
        setSingleLine(boolean)
        +
        +
      • +
      + + + +
        +
      • +

        getShouldCacheLayout

        +
        public boolean getShouldCacheLayout()
        +
        Returns whether the TextLayoutBuilder should cache the layout.
        +
        +
        Returns:
        +
        Whether the TextLayoutBuilder should cache the layout
        +
        +
      • +
      + + + +
        +
      • +

        setShouldCacheLayout

        +
        public TextLayoutBuilder setShouldCacheLayout(boolean shouldCacheLayout)
        +
        Sets whether the text layout should be cached or not. +

        + Note: If the Layout contains ClickableSpans, the layout will not be cached.

        +
        +
        Parameters:
        +
        shouldCacheLayout - True to cache the text layout, false otherwise
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        +
      • +
      + + + +
        +
      • +

        getShouldWarmText

        +
        public boolean getShouldWarmText()
        +
        Returns whether the TextLayoutBuilder should warm the layout.
        +
        +
        Returns:
        +
        Whether the TextLayoutBuilder should warm the layout
        +
        +
      • +
      + + + +
        +
      • +

        setShouldWarmText

        +
        public TextLayoutBuilder setShouldWarmText(boolean shouldWarmText)
        +
        Sets whether the text should be warmed or not. +

        + Note: Setting this true is highly effective for large blurbs of text. + This method has to be called before the draw pass.

        +
        +
        Parameters:
        +
        shouldWarmText - True to warm the text layout, false otherwise
        +
        Returns:
        +
        This TextLayoutBuilder instance
        +
        See Also:
        +
        setGlyphWarmer(GlyphWarmer)
        +
        +
      • +
      + + + +
        +
      • +

        getGlyphWarmer

        +
        public GlyphWarmer getGlyphWarmer()
        +
        Returns the GlyphWarmer used by the TextLayoutBuilder.
        +
        +
        Returns:
        +
        The GlyphWarmer for this TextLayoutBuilder
        +
        +
      • +
      + + + + + + + +
        +
      • +

        build

        +
        public Layout build()
        +
        Builds and returns a Layout.
        +
        +
        Returns:
        +
        A Layout based on the parameters set
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/docs/javadoc/com/facebook/fbui/textlayoutbuilder/glyphwarmer/GlyphWarmerImpl.html b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/glyphwarmer/GlyphWarmerImpl.html new file mode 100644 index 0000000..988f1d9 --- /dev/null +++ b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/glyphwarmer/GlyphWarmerImpl.html @@ -0,0 +1,287 @@ + + + + + +GlyphWarmerImpl (TextLayoutBuilder API) + + + + + + + + + + + + +
+
com.facebook.fbui.textlayoutbuilder.glyphwarmer
+

Class GlyphWarmerImpl

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • com.facebook.fbui.textlayoutbuilder.glyphwarmer.GlyphWarmerImpl
    • +
    +
  • +
+
+ +
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        GlyphWarmerImpl

        +
        public GlyphWarmerImpl()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        warmLayout

        +
        public void warmLayout(Layout layout)
        +
        Description copied from interface: GlyphWarmer
        +
        Warms the text layout.
        +
        +
        Specified by:
        +
        warmLayout in interface GlyphWarmer
        +
        Parameters:
        +
        layout - The layout
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/docs/javadoc/com/facebook/fbui/textlayoutbuilder/glyphwarmer/package-frame.html b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/glyphwarmer/package-frame.html new file mode 100644 index 0000000..837118d --- /dev/null +++ b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/glyphwarmer/package-frame.html @@ -0,0 +1,20 @@ + + + + + +com.facebook.fbui.textlayoutbuilder.glyphwarmer (TextLayoutBuilder API) + + + + + +

com.facebook.fbui.textlayoutbuilder.glyphwarmer

+
+

Classes

+ +
+ + diff --git a/docs/javadoc/com/facebook/fbui/textlayoutbuilder/glyphwarmer/package-summary.html b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/glyphwarmer/package-summary.html new file mode 100644 index 0000000..7e26587 --- /dev/null +++ b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/glyphwarmer/package-summary.html @@ -0,0 +1,156 @@ + + + + + +com.facebook.fbui.textlayoutbuilder.glyphwarmer (TextLayoutBuilder API) + + + + + + + + + + + +
+

Package com.facebook.fbui.textlayoutbuilder.glyphwarmer

+
+
Provides a default implementation of GlyphWarmer.
+
+

See: Description

+
+
+ + + + +

Package com.facebook.fbui.textlayoutbuilder.glyphwarmer Description

+
Provides a default implementation of GlyphWarmer.
+
+
See Also:
+
GlyphWarmer
+
+
+ + + + + + diff --git a/docs/javadoc/com/facebook/fbui/textlayoutbuilder/glyphwarmer/package-tree.html b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/glyphwarmer/package-tree.html new file mode 100644 index 0000000..503a17c --- /dev/null +++ b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/glyphwarmer/package-tree.html @@ -0,0 +1,135 @@ + + + + + +com.facebook.fbui.textlayoutbuilder.glyphwarmer Class Hierarchy (TextLayoutBuilder API) + + + + + + + + + + + +
+

Hierarchy For Package com.facebook.fbui.textlayoutbuilder.glyphwarmer

+Package Hierarchies: + +
+
+

Class Hierarchy

+ +
+ + + + + + diff --git a/docs/javadoc/com/facebook/fbui/textlayoutbuilder/package-frame.html b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/package-frame.html new file mode 100644 index 0000000..02289de --- /dev/null +++ b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/package-frame.html @@ -0,0 +1,29 @@ + + + + + +com.facebook.fbui.textlayoutbuilder (TextLayoutBuilder API) + + + + + +

com.facebook.fbui.textlayoutbuilder

+
+

Interfaces

+ +

Classes

+ +

Annotation Types

+ +
+ + diff --git a/docs/javadoc/com/facebook/fbui/textlayoutbuilder/package-summary.html b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/package-summary.html new file mode 100644 index 0000000..a5bdebc --- /dev/null +++ b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/package-summary.html @@ -0,0 +1,196 @@ + + + + + +com.facebook.fbui.textlayoutbuilder (TextLayoutBuilder API) + + + + + + + + + + + +
+

Package com.facebook.fbui.textlayoutbuilder

+
+
Provides the classes to build text Layouts easily.
+
+

See: Description

+
+
+ + + + +

Package com.facebook.fbui.textlayoutbuilder Description

+
Provides the classes to build text Layouts easily. +

+ TextLayoutBuilder is an utility class that creates + text Layouts easily. This package contains classes that interact with + TextLayoutBuilder.

+
+ + + + + + diff --git a/docs/javadoc/com/facebook/fbui/textlayoutbuilder/package-tree.html b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/package-tree.html new file mode 100644 index 0000000..f99554e --- /dev/null +++ b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/package-tree.html @@ -0,0 +1,144 @@ + + + + + +com.facebook.fbui.textlayoutbuilder Class Hierarchy (TextLayoutBuilder API) + + + + + + + + + + + +
+

Hierarchy For Package com.facebook.fbui.textlayoutbuilder

+Package Hierarchies: + +
+
+

Class Hierarchy

+ +

Interface Hierarchy

+ +

Annotation Type Hierarchy

+ +
+ + + + + + diff --git a/docs/javadoc/com/facebook/fbui/textlayoutbuilder/util/LayoutMeasureUtil.html b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/util/LayoutMeasureUtil.html new file mode 100644 index 0000000..6a81478 --- /dev/null +++ b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/util/LayoutMeasureUtil.html @@ -0,0 +1,305 @@ + + + + + +LayoutMeasureUtil (TextLayoutBuilder API) + + + + + + + + + + + + +
+
com.facebook.fbui.textlayoutbuilder.util
+

Class LayoutMeasureUtil

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • com.facebook.fbui.textlayoutbuilder.util.LayoutMeasureUtil
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class LayoutMeasureUtil
    +extends Object
    +
    Utility Class for measuring text Layouts.
    +
  • +
+
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        LayoutMeasureUtil

        +
        public LayoutMeasureUtil()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getWidth

        +
        public static int getWidth(Layout layout)
        +
        Returns the width of the layout.
        +
        +
        Parameters:
        +
        layout - The layout
        +
        Returns:
        +
        The width of the layout
        +
        +
      • +
      + + + +
        +
      • +

        getHeight

        +
        public static int getHeight(Layout layout)
        +
        Prior to version 20, If the Layout specifies extra space between lines (either by spacingmult + or spacingadd) the StaticLayout would erroneously add this space after the last line as well. + This bug was fixed in version 20. This method calculates the extra space and reduces the height + by that amount.
        +
        +
        Parameters:
        +
        layout - The layout
        +
        Returns:
        +
        The height of the layout
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/docs/javadoc/com/facebook/fbui/textlayoutbuilder/util/package-frame.html b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/util/package-frame.html new file mode 100644 index 0000000..ee07f21 --- /dev/null +++ b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/util/package-frame.html @@ -0,0 +1,20 @@ + + + + + +com.facebook.fbui.textlayoutbuilder.util (TextLayoutBuilder API) + + + + + +

com.facebook.fbui.textlayoutbuilder.util

+
+

Classes

+ +
+ + diff --git a/docs/javadoc/com/facebook/fbui/textlayoutbuilder/util/package-summary.html b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/util/package-summary.html new file mode 100644 index 0000000..7cd0ce7 --- /dev/null +++ b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/util/package-summary.html @@ -0,0 +1,151 @@ + + + + + +com.facebook.fbui.textlayoutbuilder.util (TextLayoutBuilder API) + + + + + + + + + + + +
+

Package com.facebook.fbui.textlayoutbuilder.util

+
+
Provides an utility class to measure text Layouts.
+
+

See: Description

+
+
+
    +
  • + + + + + + + + + + + + +
    Class Summary 
    ClassDescription
    LayoutMeasureUtil +
    Utility Class for measuring text Layouts.
    +
    +
  • +
+ + + +

Package com.facebook.fbui.textlayoutbuilder.util Description

+
Provides an utility class to measure text Layouts.
+
+ + + + + + diff --git a/docs/javadoc/com/facebook/fbui/textlayoutbuilder/util/package-tree.html b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/util/package-tree.html new file mode 100644 index 0000000..6944cfa --- /dev/null +++ b/docs/javadoc/com/facebook/fbui/textlayoutbuilder/util/package-tree.html @@ -0,0 +1,135 @@ + + + + + +com.facebook.fbui.textlayoutbuilder.util Class Hierarchy (TextLayoutBuilder API) + + + + + + + + + + + +
+

Hierarchy For Package com.facebook.fbui.textlayoutbuilder.util

+Package Hierarchies: + +
+
+

Class Hierarchy

+ +
+ + + + + + diff --git a/docs/javadoc/constant-values.html b/docs/javadoc/constant-values.html new file mode 100644 index 0000000..9816a0e --- /dev/null +++ b/docs/javadoc/constant-values.html @@ -0,0 +1,172 @@ + + + + + +Constant Field Values (TextLayoutBuilder API) + + + + + + + + + + + +
+

Constant Field Values

+

Contents

+ +
+
+ + +

com.facebook.*

+ +
+ + + + + + diff --git a/docs/javadoc/deprecated-list.html b/docs/javadoc/deprecated-list.html new file mode 100644 index 0000000..7b7420e --- /dev/null +++ b/docs/javadoc/deprecated-list.html @@ -0,0 +1,122 @@ + + + + + +Deprecated List (TextLayoutBuilder API) + + + + + + + + +
+ + + + + + + +
+ + +
+

Deprecated API

+

Contents

+
+ +
+ + + + + + + +
+ + + + diff --git a/docs/javadoc/help-doc.html b/docs/javadoc/help-doc.html new file mode 100644 index 0000000..62832d8 --- /dev/null +++ b/docs/javadoc/help-doc.html @@ -0,0 +1,223 @@ + + + + + +API Help (TextLayoutBuilder API) + + + + + + + + +
+ + + + + + + +
+ + +
+

How This API Document Is Organized

+
This API (Application Programming Interface) document has pages corresponding to the items in the navigation bar, described as follows.
+
+
+
    +
  • +

    Overview

    +

    The Overview page is the front page of this API document and provides a list of all packages with a summary for each. This page can also contain an overall description of the set of packages.

    +
  • +
  • +

    Package

    +

    Each package has a page that contains a list of its classes and interfaces, with a summary for each. This page can contain six categories:

    +
      +
    • Interfaces (italic)
    • +
    • Classes
    • +
    • Enums
    • +
    • Exceptions
    • +
    • Errors
    • +
    • Annotation Types
    • +
    +
  • +
  • +

    Class/Interface

    +

    Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a class/interface description, summary tables, and detailed member descriptions:

    +
      +
    • Class inheritance diagram
    • +
    • Direct Subclasses
    • +
    • All Known Subinterfaces
    • +
    • All Known Implementing Classes
    • +
    • Class/interface declaration
    • +
    • Class/interface description
    • +
    +
      +
    • Nested Class Summary
    • +
    • Field Summary
    • +
    • Constructor Summary
    • +
    • Method Summary
    • +
    +
      +
    • Field Detail
    • +
    • Constructor Detail
    • +
    • Method Detail
    • +
    +

    Each summary entry contains the first sentence from the detailed description for that item. The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.

    +
  • +
  • +

    Annotation Type

    +

    Each annotation type has its own separate page with the following sections:

    +
      +
    • Annotation Type declaration
    • +
    • Annotation Type description
    • +
    • Required Element Summary
    • +
    • Optional Element Summary
    • +
    • Element Detail
    • +
    +
  • +
  • +

    Enum

    +

    Each enum has its own separate page with the following sections:

    +
      +
    • Enum declaration
    • +
    • Enum description
    • +
    • Enum Constant Summary
    • +
    • Enum Constant Detail
    • +
    +
  • +
  • +

    Tree (Class Hierarchy)

    +

    There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. The classes are organized by inheritance structure starting with java.lang.Object. The interfaces do not inherit from java.lang.Object.

    +
      +
    • When viewing the Overview page, clicking on "Tree" displays the hierarchy for all packages.
    • +
    • When viewing a particular package, class or interface page, clicking "Tree" displays the hierarchy for only that package.
    • +
    +
  • +
  • +

    Deprecated API

    +

    The Deprecated API page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to improvements, and a replacement API is usually given. Deprecated APIs may be removed in future implementations.

    +
  • +
  • +

    Index

    +

    The Index contains an alphabetic list of all classes, interfaces, constructors, methods, and fields.

    +
  • +
  • +

    Prev/Next

    +

    These links take you to the next or previous class, interface, package, or related page.

    +
  • +
  • +

    Frames/No Frames

    +

    These links show and hide the HTML frames. All pages are available with or without frames.

    +
  • +
  • +

    All Classes

    +

    The All Classes link shows all classes and interfaces except non-static nested types.

    +
  • +
  • +

    Serialized Form

    +

    Each serializable or externalizable class has a description of its serialization fields and methods. This information is of interest to re-implementors, not to developers using the API. While there is no link in the navigation bar, you can get to this information by going to any serialized class and clicking "Serialized Form" in the "See also" section of the class description.

    +
  • +
  • +

    Constant Field Values

    +

    The Constant Field Values page lists the static final fields and their values.

    +
  • +
+This help file applies to API documentation generated using the standard doclet.
+ +
+ + + + + + + +
+ + + + diff --git a/docs/javadoc/index-all.html b/docs/javadoc/index-all.html new file mode 100644 index 0000000..f949dc1 --- /dev/null +++ b/docs/javadoc/index-all.html @@ -0,0 +1,431 @@ + + + + + +Index (TextLayoutBuilder API) + + + + + + + + +
+ + + + + + + +
+ + +
B C D G L M R S T U W  + + +

B

+
+
build() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Builds and returns a Layout.
+
+
+ + + +

C

+
+
com.facebook.fbui.textlayoutbuilder - package com.facebook.fbui.textlayoutbuilder
+
+
Provides the classes to build text Layouts easily.
+
+
com.facebook.fbui.textlayoutbuilder.glyphwarmer - package com.facebook.fbui.textlayoutbuilder.glyphwarmer
+
+
Provides a default implementation of GlyphWarmer.
+
+
com.facebook.fbui.textlayoutbuilder.util - package com.facebook.fbui.textlayoutbuilder.util
+
+
Provides an utility class to measure text Layouts.
+
+
+ + + +

D

+
+
DEFAULT_MAX_LINES - Static variable in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
 
+
+ + + +

G

+
+
getAlignment() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns the text alignment for this TextLayoutBuilder.
+
+
getDrawableState() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns the drawable state for this TextLayoutBuilder.
+
+
getEllipsize() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns the text ellipsize for this TextLayoutBuilder.
+
+
getGlyphWarmer() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns the GlyphWarmer used by the TextLayoutBuilder.
+
+
getHeight(Layout) - Static method in class com.facebook.fbui.textlayoutbuilder.util.LayoutMeasureUtil
+
+
Prior to version 20, If the Layout specifies extra space between lines (either by spacingmult + or spacingadd) the StaticLayout would erroneously add this space after the last line as well.
+
+
getIncludeFontPadding() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns whether this TextLayoutBuilder should include font padding.
+
+
getLinkColor() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns the link color for this TextLayoutBuilder.
+
+
getMaxLines() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns the number of max lines used by this TextLayoutBuilder.
+
+
getShouldCacheLayout() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns whether the TextLayoutBuilder should cache the layout.
+
+
getShouldWarmText() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns whether the TextLayoutBuilder should warm the layout.
+
+
getSingleLine() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns whether the TextLayoutBuilder should show a single line.
+
+
getText() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns the text that would be packed in a layout by this TextLayoutBuilder.
+
+
getTextColor() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns the text color for this TextLayoutBuilder.
+
+
getTextDirection() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns the text direction for this TextLayoutBuilder.
+
+
getTextSize() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns the text size for this TextLayoutBuilder.
+
+
getTextSpacingExtra() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns the text spacing extra for this TextLayoutBuilder.
+
+
getTextSpacingMultiplier() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns the text spacing multiplier for this TextLayoutBuilder.
+
+
getTypeface() - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Returns the typeface for this TextLayoutBuilder.
+
+
getWidth(Layout) - Static method in class com.facebook.fbui.textlayoutbuilder.util.LayoutMeasureUtil
+
+
Returns the width of the layout.
+
+
GlyphWarmer - Interface in com.facebook.fbui.textlayoutbuilder
+
+
Specifies an interface that a class has to implement + to warm the text Layout in the background.
+
+
GlyphWarmerImpl - Class in com.facebook.fbui.textlayoutbuilder.glyphwarmer
+
+
Default GlyphWarmer that runs a HandlerThread + to draw a text Layout on a Picture.
+
+
GlyphWarmerImpl() - Constructor for class com.facebook.fbui.textlayoutbuilder.glyphwarmer.GlyphWarmerImpl
+
 
+
+ + + +

L

+
+
LayoutMeasureUtil - Class in com.facebook.fbui.textlayoutbuilder.util
+
+
Utility Class for measuring text Layouts.
+
+
LayoutMeasureUtil() - Constructor for class com.facebook.fbui.textlayoutbuilder.util.LayoutMeasureUtil
+
 
+
+ + + +

M

+
+
MEASURE_MODE_AT_MOST - Static variable in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
 
+
MEASURE_MODE_EXACTLY - Static variable in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
 
+
MEASURE_MODE_UNSPECIFIED - Static variable in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
 
+
+ + + +

R

+
+
ResourceTextLayoutHelper - Class in com.facebook.fbui.textlayoutbuilder
+
+
An utility class to update a TextLayoutBuilder from an Android resource.
+
+
ResourceTextLayoutHelper() - Constructor for class com.facebook.fbui.textlayoutbuilder.ResourceTextLayoutHelper
+
 
+
+ + + +

S

+
+
setAlignment(Layout.Alignment) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets text alignment for the layout.
+
+
setDrawableState(int[]) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Updates the text colors based on the drawable state.
+
+
setEllipsize(TextUtils.TruncateAt) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets the ellipsis location for the layout.
+
+
setGlyphWarmer(GlyphWarmer) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets the glyph warmer to use.
+
+
setIncludeFontPadding(boolean) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Set whether the text Layout includes extra top and bottom padding to make + room for accents that go above the normal ascent and descent.
+
+
setLinkColor(int) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets the link color for the text in the layout.
+
+
setMaxLines(int) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets a maximum number of lines to be shown by the Layout.
+
+
setShadowLayer(float, float, float, int) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets the shadow layer for the layout.
+
+
setShouldCacheLayout(boolean) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets whether the text layout should be cached or not.
+
+
setShouldWarmText(boolean) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets whether the text should be warmed or not.
+
+
setSingleLine(boolean) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets whether the text should be in a single line or not.
+
+
setText(CharSequence) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets the text for the layout.
+
+
setTextAppearance(TextLayoutBuilder, Context, int) - Static method in class com.facebook.fbui.textlayoutbuilder.ResourceTextLayoutHelper
+
+
Sets a text appearance for the layout.
+
+
setTextColor(int) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets the text color for the layout.
+
+
setTextColor(ColorStateList) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets the text color for the layout.
+
+
setTextDirection(TextDirectionHeuristicCompat) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets the text direction heuristic for the layout.
+
+
setTextSize(int) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets the text size for the layout.
+
+
setTextSpacingExtra(float) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets the text extra spacing for the layout.
+
+
setTextSpacingMultiplier(float) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets the line spacing multiplier for the layout.
+
+
setTextStyle(int) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets a text style for the layout.
+
+
setTypeface(Typeface) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets the typeface used by this TextLayoutBuilder.
+
+
setWidth(int) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets the intended width of the text layout.
+
+
setWidth(int, int) - Method in class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
+
Sets the intended width of the text layout while respecting the measure mode.
+
+
+ + + +

T

+
+
TextLayoutBuilder - Class in com.facebook.fbui.textlayoutbuilder
+
+
An utility class to create text Layouts easily.
+
+
TextLayoutBuilder() - Constructor for class com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder
+
 
+
TextLayoutBuilder.MeasureMode - Annotation Type in com.facebook.fbui.textlayoutbuilder
+
+
Measure mode constants similar to View.MeasureSpec
+
+
+ + + +

U

+
+
updateFromStyleResource(TextLayoutBuilder, Context, int) - Static method in class com.facebook.fbui.textlayoutbuilder.ResourceTextLayoutHelper
+
+
Sets the values for a TextLayoutBuilder from a style resource.
+
+
updateFromStyleResource(TextLayoutBuilder, Context, int, int) - Static method in class com.facebook.fbui.textlayoutbuilder.ResourceTextLayoutHelper
+
+
Sets the values for a TextLayoutBuilder from a style resource or a themed attribute.
+
+
updateFromStyleResource(TextLayoutBuilder, Context, AttributeSet, int, int) - Static method in class com.facebook.fbui.textlayoutbuilder.ResourceTextLayoutHelper
+
+
Sets the values for a TextLayoutBuilder from a style resource or a themed attribute.
+
+
+ + + +

W

+
+
warmLayout(Layout) - Method in class com.facebook.fbui.textlayoutbuilder.glyphwarmer.GlyphWarmerImpl
+
 
+
warmLayout(Layout) - Method in interface com.facebook.fbui.textlayoutbuilder.GlyphWarmer
+
+
Warms the text layout.
+
+
+B C D G L M R S T U W 
+ +
+ + + + + + + +
+ + + + diff --git a/docs/javadoc/index.html b/docs/javadoc/index.html new file mode 100644 index 0000000..65cf246 --- /dev/null +++ b/docs/javadoc/index.html @@ -0,0 +1,74 @@ + + + + + +TextLayoutBuilder API + + + + + + + + + +<noscript> +<div>JavaScript is disabled on your browser.</div> +</noscript> +<h2>Frame Alert</h2> +<p>This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client. Link to <a href="overview-summary.html">Non-frame version</a>.</p> + + + diff --git a/docs/javadoc/overview-frame.html b/docs/javadoc/overview-frame.html new file mode 100644 index 0000000..00830c1 --- /dev/null +++ b/docs/javadoc/overview-frame.html @@ -0,0 +1,23 @@ + + + + + +Overview List (TextLayoutBuilder API) + + + + + + + +

 

+ + diff --git a/docs/javadoc/overview-summary.html b/docs/javadoc/overview-summary.html new file mode 100644 index 0000000..af4b821 --- /dev/null +++ b/docs/javadoc/overview-summary.html @@ -0,0 +1,150 @@ + + + + + +Overview (TextLayoutBuilder API) + + + + + + + + +
+ + + + + + + +
+ + +
+

TextLayoutBuilder API

+
+
+ + + + + + + + + + + + + + + + + + + + +
Packages 
PackageDescription
com.facebook.fbui.textlayoutbuilder +
Provides the classes to build text Layouts easily.
+
com.facebook.fbui.textlayoutbuilder.glyphwarmer +
Provides a default implementation of GlyphWarmer.
+
com.facebook.fbui.textlayoutbuilder.util +
Provides an utility class to measure text Layouts.
+
+
+ +
+ + + + + + + +
+ + + + diff --git a/docs/javadoc/overview-tree.html b/docs/javadoc/overview-tree.html new file mode 100644 index 0000000..90bdbd5 --- /dev/null +++ b/docs/javadoc/overview-tree.html @@ -0,0 +1,148 @@ + + + + + +Class Hierarchy (TextLayoutBuilder API) + + + + + + + + +
+ + + + + + + +
+ + + +
+

Class Hierarchy

+ +

Interface Hierarchy

+ +

Annotation Type Hierarchy

+ +
+ +
+ + + + + + + +
+ + + + diff --git a/docs/javadoc/package-list b/docs/javadoc/package-list new file mode 100644 index 0000000..56e7f61 --- /dev/null +++ b/docs/javadoc/package-list @@ -0,0 +1,3 @@ +com.facebook.fbui.textlayoutbuilder +com.facebook.fbui.textlayoutbuilder.glyphwarmer +com.facebook.fbui.textlayoutbuilder.util diff --git a/docs/javadoc/script.js b/docs/javadoc/script.js new file mode 100644 index 0000000..b346356 --- /dev/null +++ b/docs/javadoc/script.js @@ -0,0 +1,30 @@ +function show(type) +{ + count = 0; + for (var key in methods) { + var row = document.getElementById(key); + if ((methods[key] & type) != 0) { + row.style.display = ''; + row.className = (count++ % 2) ? rowColor : altColor; + } + else + row.style.display = 'none'; + } + updateTabs(type); +} + +function updateTabs(type) +{ + for (var value in tabs) { + var sNode = document.getElementById(tabs[value][0]); + var spanNode = sNode.firstChild; + if (value == type) { + sNode.className = activeTableTab; + spanNode.innerHTML = tabs[value][1]; + } + else { + sNode.className = tableTab; + spanNode.innerHTML = "" + tabs[value][1] + ""; + } + } +} diff --git a/docs/javadoc/stylesheet.css b/docs/javadoc/stylesheet.css new file mode 100644 index 0000000..96e35e7 --- /dev/null +++ b/docs/javadoc/stylesheet.css @@ -0,0 +1,571 @@ +/* Javadoc style sheet */ +/* +Overall document style +*/ + +body { + background-color: #ffffff; + color: #4B4F56; + font-family: -apple-system, Arial, Helvetica, sans-serif; + font-size: 14px; + margin: 0; +} +a:link, a:visited { + text-decoration: none; + color: #34255D; +} +a:hover, a:focus { + text-decoration: none; + color: #6A51B2; +} +a:active { + text-decoration: none; + color: #34255D; +} +a[name] { + color: #1D2129; +} +a[name]:hover { + text-decoration: none; + color: #1D2129; +} +pre { + font-family: Consolas, Menlo, Monaco, monospace; + font-size: 14px; +} +h1 { + font-size: 20px; +} +h2 { + font-size: 18px; +} +h3 { + font-size: 16px; + font-style: italic; +} +h4 { + font-size: 13px; +} +h5 { + font-size: 12px; +} +h6 { + font-size: 11px; +} +ul { + list-style-type: disc; +} +code, tt { + font-family: Consolas, Menlo, Monaco, monospace; + font-size: 14px; + padding-top: 4px; + margin-top: 8px; + line-height: 1.4em; +} +dt code { + font-family: Consolas, Menlo, Monaco, monospace; + font-size: 14px; + padding-top: 4px; +} +table tr td dt code { + font-family: Consolas, Menlo, Monaco, monospace; + font-size: 14px; + vertical-align: top; + padding-top: 4px; +} +sup { + font-size: 8px; +} +/* +Document title and Copyright styles +*/ +.clear { + clear: both; + height: 0px; + overflow: hidden; +} +.aboutLanguage { + float: right; + padding: 0px 21px; + font-size: 11px; + z-index: 200; + margin-top: -9px; +} +.legalCopy { + margin-left: .5em; +} +.bar a, .bar a:link, .bar a:visited, .bar a:active { + color: #FFFFFF; + text-decoration: none; +} +.bar a:hover, .bar a:focus { + color: #9D87D2; +} +.tab { + background-color: #0066FF; + color: #ffffff; + padding: 8px; + width: 5em; + font-weight: bold; +} +/* +Navigation bar styles +*/ +.bar { + background-color: #6A51B2; + color: #FFFFFF; + padding: .8em .5em .4em .8em; + height: auto;/*height: 1.8em;*/ + font-size: 11px; + margin: 0; +} +.topNav { + background-color: #6A51B2; + color: #FFFFFF; + float: left; + padding: 0; + width: 100%; + clear: right; + height: 2.8em; + padding-top: 10px; + overflow: hidden; + font-size: 12px; +} +.bottomNav { + margin-top: 10px; + background-color: #6A51B2; + color: #FFFFFF; + float: left; + padding: 0; + width: 100%; + clear: right; + height: 2.8em; + padding-top: 10px; + overflow: hidden; + font-size: 12px; +} +.subNav { + background-color: #E9EBEE; + float: left; + width: 100%; + overflow: hidden; + font-size: 12px; +} +.subNav div { + clear: left; + float: left; + padding: 0 0 5px 6px; + text-transform: uppercase; +} +ul.navList, ul.subNavList { + float: left; + margin: 0 25px 0 0; + padding: 0; +} +ul.navList li{ + list-style: none; + float: left; + padding: 5px 6px; + text-transform: uppercase; +} +ul.subNavList li{ + list-style: none; + float: left; +} +.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited { + color: #FFFFFF; + text-decoration: none; + text-transform: uppercase; +} +.topNav a:hover, .bottomNav a:hover { + text-decoration: none; + color: #9D87D2; + text-transform: uppercase; +} +.navBarCell1Rev { + background-color: #DDD5F0; + color: #253441; + margin: auto 5px; +} +.skipNav { + position: absolute; + top: auto; + left: -9999px; + overflow: hidden; +} +/* +Page header and footer styles +*/ +.header, .footer { + clear: both; + margin: 0 20px; + padding: 5px 0 0 0; +} +.indexHeader { + margin: 10px; + position: relative; +} +.indexHeader span{ + margin-right: 15px; +} +.indexHeader h1 { + font-size: 13px; +} +.title { + color: #1D2129; + margin: 10px 0; +} +.subTitle { + margin: 5px 0 0 0; +} +.header ul { + margin: 0 0 15px 0; + padding: 0; +} +.footer ul { + margin: 20px 0 5px 0; +} +.header ul li, .footer ul li { + list-style: none; + font-size: 13px; +} +/* +Heading styles +*/ +div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { + background-color: #E9EBEE; + border: 1px solid #E9EBEE; + margin: 0 0 6px -8px; + padding: 7px 5px; +} +ul.blockList ul.blockList ul.blockList li.blockList h3 { + background-color: #E9EBEE; + border: 1px solid #E9EBEE; + margin: 0 0 6px -8px; + padding: 7px 5px; +} +ul.blockList ul.blockList li.blockList h3 { + padding: 0; + margin: 15px 0; +} +ul.blockList li.blockList h2 { + padding: 0px 0 20px 0; +} +/* +Page layout container styles +*/ +.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer { + clear: both; + padding: 10px 20px; + position: relative; +} +.indexContainer { + margin: 10px; + position: relative; + font-size: 12px; +} +.indexContainer h2 { + font-size: 13px; + padding: 0 0 3px 0; +} +.indexContainer ul { + margin: 0; + padding: 0; +} +.indexContainer ul li { + list-style: none; + padding-top: 2px; +} +.contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { + font-size: 12px; + font-weight: bold; + margin: 10px 0 0 0; + color: #1D2129; +} +.contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { + margin: 5px 0 10px 0px; + font-size: 14px; + font-family: Consolas, Menlo, Monaco, monospace; +} +.serializedFormContainer dl.nameValue dt { + margin-left: 1px; + font-size: 1.1em; + display: inline; + font-weight: bold; +} +.serializedFormContainer dl.nameValue dd { + margin: 0 0 0 1px; + font-size: 1.1em; + display: inline; +} +/* +List styles +*/ +ul.horizontal li { + display: inline; + font-size: 0.9em; +} +ul.inheritance { + margin: 0; + padding: 0; +} +ul.inheritance li { + display: inline; + list-style: none; +} +ul.inheritance li ul.inheritance { + margin-left: 15px; + padding-left: 15px; + padding-top: 1px; +} +ul.blockList, ul.blockListLast { + margin: 10px 0 10px 0; + padding: 0; +} +ul.blockList li.blockList, ul.blockListLast li.blockList { + list-style: none; + margin-bottom: 15px; + line-height: 1.4; +} +ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { + padding: 0px 20px 5px 10px; + border: 1px solid #ededed; + background-color: #f8f8f8; +} +ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList { + padding: 0 0 5px 8px; + background-color: #ffffff; + border: none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { + margin-left: 0; + padding-left: 0; + padding-bottom: 15px; + border: none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { + list-style: none; + border-bottom: none; + padding-bottom: 0; +} +table tr td dl, table tr td dl dt, table tr td dl dd { + margin-top: 0; + margin-bottom: 1px; +} +/* +Table styles +*/ +.overviewSummary, .memberSummary, .typeSummary, .useSummary, .constantsSummary, .deprecatedSummary { + width: 100%; + border-left: 1px solid #E9EBEE; + border-right: 1px solid #E9EBEE; + border-bottom: 1px solid #E9EBEE; +} +.overviewSummary, .memberSummary { + padding: 0px; +} +.overviewSummary caption, .memberSummary caption, .typeSummary caption, +.useSummary caption, .constantsSummary caption, .deprecatedSummary caption { + position: relative; + text-align: left; + background-repeat: no-repeat; + color: #4B4F56; + font-weight: bold; + clear: none; + overflow: hidden; + padding: 0px; + padding-top: 10px; + padding-left: 1px; + margin: 0px; + white-space: pre; +} +.overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link, +.useSummary caption a:link, .constantsSummary caption a:link, .deprecatedSummary caption a:link, +.overviewSummary caption a:hover, .memberSummary caption a:hover, .typeSummary caption a:hover, +.useSummary caption a:hover, .constantsSummary caption a:hover, .deprecatedSummary caption a:hover, +.overviewSummary caption a:active, .memberSummary caption a:active, .typeSummary caption a:active, +.useSummary caption a:active, .constantsSummary caption a:active, .deprecatedSummary caption a:active, +.overviewSummary caption a:visited, .memberSummary caption a:visited, .typeSummary caption a:visited, +.useSummary caption a:visited, .constantsSummary caption a:visited, .deprecatedSummary caption a:visited { + color: #FFFFFF; +} +.overviewSummary caption span, .memberSummary caption span, .typeSummary caption span, +.useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span { + white-space: nowrap; + padding-top: 5px; + padding-left: 12px; + padding-right: 12px; + padding-bottom: 7px; + display: inline-block; + float: left; + background-color: #DDD5F0; + border: none; + height: 16px; +} +.memberSummary caption span.activeTableTab span { + white-space: nowrap; + padding-top: 5px; + padding-left: 12px; + padding-right: 12px; + margin-right: 3px; + display: inline-block; + float: left; + background-color: #DDD5F0; + height: 16px; +} +.memberSummary caption span.tableTab span { + white-space: nowrap; + padding-top: 5px; + padding-left: 12px; + padding-right: 12px; + margin-right: 3px; + display: inline-block; + float: left; + background-color: #6A51B2; + height: 16px; +} +.memberSummary caption span.tableTab, .memberSummary caption span.activeTableTab { + padding-top: 0px; + padding-left: 0px; + padding-right: 0px; + background-image: none; + float: none; + display: inline; +} +.overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd, +.useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd { + display: none; + width: 5px; + position: relative; + float: left; + background-color: #DDD5F0; +} +.memberSummary .activeTableTab .tabEnd { + display: none; + width: 5px; + margin-right: 3px; + position: relative; + float: left; + background-color: #DDD5F0; +} +.memberSummary .tableTab .tabEnd { + display: none; + width: 5px; + margin-right: 3px; + position: relative; + background-color: #6A51B2; + float: left; + +} +.overviewSummary td, .memberSummary td, .typeSummary td, +.useSummary td, .constantsSummary td, .deprecatedSummary td { + text-align: left; + padding: 0px 0px 12px 10px; +} +th.colOne, th.colFirst, th.colLast, .useSummary th, .constantsSummary th, +td.colOne, td.colFirst, td.colLast, .useSummary td, .constantsSummary td{ + vertical-align: top; + padding-right: 0px; + padding-top: 8px; + padding-bottom: 3px; +} +th.colFirst, th.colLast, th.colOne, .constantsSummary th { + background: #E9EBEE; + text-align: left; + padding: 8px 3px 3px 7px; +} +td.colFirst, th.colFirst { + white-space: nowrap; + font-size: 13px; +} +td.colLast, th.colLast { + font-size: 13px; +} +td.colOne, th.colOne { + font-size: 13px; +} +.overviewSummary td.colFirst, .overviewSummary th.colFirst, +.useSummary td.colFirst, .useSummary th.colFirst, +.overviewSummary td.colOne, .overviewSummary th.colOne, +.memberSummary td.colFirst, .memberSummary th.colFirst, +.memberSummary td.colOne, .memberSummary th.colOne, +.typeSummary td.colFirst{ + width: 25%; + vertical-align: top; +} +td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover { + font-weight: bold; +} +.tableSubHeadingColor { + background-color: #EEEEFF; +} +.altColor { + background-color: #FFFFFF; +} +.rowColor { + background-color: #F6F7F9; +} +/* +Content styles +*/ +.description pre { + margin-top: 0; +} +.deprecatedContent { + margin: 0; + padding: 10px 0; +} +.docSummary { + padding: 0; +} + +ul.blockList ul.blockList ul.blockList li.blockList h3 { + font-style: normal; +} + +div.block { + font-size: 14px; +} + +td.colLast div { + padding-top: 0px; +} + + +td.colLast a { + padding-bottom: 3px; +} +/* +Formatting effect styles +*/ +.sourceLineNo { + color: green; + padding: 0 30px 0 0; +} +h1.hidden { + visibility: hidden; + overflow: hidden; + font-size: 10px; +} +.block { + display: block; + margin: 3px 10px 2px 0px; + color: #4B4F56; +} +.deprecatedLabel, .descfrmTypeLabel, .memberNameLabel, .memberNameLink, +.overrideSpecifyLabel, .packageHierarchyLabel, .paramLabel, .returnLabel, +.seeLabel, .simpleTagLabel, .throwsLabel, .typeNameLabel, .typeNameLink { + font-weight: bold; +} +.deprecationComment, .emphasizedPhrase, .interfaceName { + font-style: italic; +} + +div.block div.block span.deprecationComment, div.block div.block span.emphasizedPhrase, +div.block div.block span.interfaceName { + font-style: normal; +} + +div.contentContainer ul.blockList li.blockList h2{ + padding-bottom: 0px; +} diff --git a/docs/logo.png b/docs/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..524199de7cefd39ca81413fbc61019f1d813146e GIT binary patch literal 3589 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 + task androidJavadoc(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs + exclude '**/pom.xml' + exclude '**/proguard_annotations.pro' + exclude '**/BUCK' + classpath += files(android.bootClasspath) + classpath += configurations.compile + title = 'TextLayoutBuilder API' + options { + links 'http://docs.oracle.com/javase/7/docs/api/' + linksOffline 'https://developer.android.com/reference/', "file://${android.sdkDirectory}/docs/reference/" + } + } + + androidJavadoc.doLast { + copy { + from new File(projectDir, 'src/javadoc/stylesheet.css') + into new File(buildDir, 'docs/javadoc/') + } + } + + task androidJavadocJar(type: Jar) { + classifier = 'javadoc' + from androidJavadoc.destinationDir + } + + task androidSourcesJar(type: Jar) { + classifier = 'sources' + from android.sourceSets.main.java.srcDirs + exclude '**/BUCK' + } + + android.libraryVariants.all { variant -> + def name = variant.name.capitalize() + task "jar${name}"(type: Jar, dependsOn: variant.javaCompile) { + from variant.javaCompile.destinationDir + } + } + + artifacts { + archives androidSourcesJar + archives androidJavadocJar + } +} diff --git a/gradle/bintray.gradle b/gradle/bintray.gradle new file mode 100644 index 0000000..a6892fe --- /dev/null +++ b/gradle/bintray.gradle @@ -0,0 +1,57 @@ +// Upload to Bintray +apply plugin: 'com.jfrog.bintray' + +def getBintrayUsername() { + return hasProperty('bintrayUsername') ? property('bintrayUsername') : System.getenv('BINTRAY_USERNAME') +} + +def getBintrayApiKey() { + return hasProperty('bintrayApiKey') ? property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') +} + +def getBintrayGpgPassword() { + return hasProperty('bintrayGpgPassword') ? property('bintrayGpgPassword') : System.getenv('BINTRAY_GPG_PASSWORD') +} + +def getMavenCentralUsername() { + return hasProperty('mavenCentralUsername') ? property('mavenCentralUsername') : System.getenv('MAVEN_CENTRAL_USERNAME') +} + +def getMavenCentralPassword() { + return hasProperty('mavenCentralPassword') ? property('mavenCentralPassword') : System.getenv('MAVEN_CENTRAL_PASSWORD') +} + +def shouldSyncWithMavenCentral() { + return hasProperty('syncWithMavenCentral') ? property('syncWithMavenCentral').toBoolean() : false +} + +bintray { + user = getBintrayUsername() + key = getBintrayApiKey() + configurations = ['archives'] + pkg { + repo = bintrayRepo + userOrg = bintrayUserOrg + name = bintrayName + desc = bintrayDescription + websiteUrl = projectUrl + issueTrackerUrl = issuesUrl + vcsUrl = scmUrl + licenses = projectLicenses + publish = true + publicDownloadNumbers = true + version { + desc = bintrayDescription + gpg { + sign = true + passphrase = getBintrayGpgPassword() + } + mavenCentralSync { + sync = shouldSyncWithMavenCentral() + user = getMavenCentralUsername() + password = getMavenCentralPassword() + close = '1' // If set to 0, you have to manually click release + } + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..deedc7fa5e6310eac3148a7dd0b1f069b07364cb GIT binary patch literal 52818 zcmagFW0WpIwk=xLuG(eWwr$(CZEKfp+qP}nwr%_FbGzR;xBK;dFUMG4=8qL4GvZsA zE7lA-Nnj8t000OG0CW#nae%)U(0~2>y&(UJw6GFCwYZE3Eii!GzbJwQ6GlMey#wI?@jA$V`!0~bud{V9{g+Srcb#AV)G>9?H?lJR z|5Qc%S5;RBeLFj2hyT|QGk+tKg1@Rue}(Wr4-v9;wXw3*HzJ~^F|^Wmbo7pthU%w- z3)(Sb)}VBu_5ZaJoZW|Ohfl-BZzX62DK1{#mGKL9H*XNh{(|e68)wq1=H&nqPq4oi z%|O7bnKfm?yNp=By{T$W1?fU!6I8#Mv8}nA>6|R1f*Oq^FvvNak`#*C{X$4va>UoS zA`(Erflj173T0bTR*Vy4rJu~FU5UXK;(<5T2_25xs{}W2mH=8n1Pu%~Bx(T0nHt;s z-&T2OJ7^i{@856tcZr4mf99y@?&xG}E$3kScd?wzjUE3!xw-Q@JDC~VIGG#jJJ~w? zV-boJt!)wb;e1fYLPqBH%k-*})|Wk$j>2u{^e`Z!!XW9T%cZ4wt@VLTt6hz38}UJg!HZUDyJEC{0fA%B4aTas_G)I~=ju_&r7 zUt=R`wptSW9_elN^MoEl)!8l64sKQCG7?+tFV<5l_w;jH;ATg;r{;YoH&__}dx33x zeDpz*Ds4ukuf%;MB$jzLUWHe1Cm^_K)V(TihDco5rAUNczQBX4KYk!X7<5;MHJ-2* z-+m0*Naz$)a;3cl^%>2`c=)A)maHjorP!uJmSLER3I>fSQ}^xXduW4~$jM!1u*(B1 z*3GCW*_IEE$hoCYHYsjI2isq56{?zzBYO-)VNQ<1pjL?CXhcudoOGVZ@jiM(fDgk} zE9WoidJEpVYhg6Px7IJnHII#h>DFKS;X7bF`lZ4SSUH^uAn3yP=sxQZ;*B={o*lgP z4y`HUO(iT&Yo;9T8-kWCE&eHL;ldz7prmH$sGby`5E`h+RZf3c(#TeRcA=AIFI73G zYr^kqKloTRPpFZfC7G;)gwi|%_aP+%t*(&}fHz{SQKb)LrA3&*_xlaLO+r5Es0aUh zTPD-6PiB3XT|w9G4Enev%)y{i%SSD`7uqIroSPIA(_DX{=`a|Qka}ISZwk=bIo9`= z>e%{Wk^CTXYO4&&+9K`$gp&XA+mlN*$MV0{w((a8{>ig?h(7`{G zXU9nJolrVY26vqmP{90hk2)<3EE1gOPCOalxV<3=oJr^qV=13+4_;fi04S%PrydXx zKKYcy%(4&(XCx=8(}`qj`lvy=<4l^S3V{uT_-b1Q@`-6Grm)--p5F9zr7wZ}ji2gM z7lQq28Hq)~qzbj;xA}0v%ozQ*hO})GYtM-htwfRE1;>gZe0Fl+ZGk9S6V{T>SF4X! zH@&{V|2k8UGLJ2-zy2lv*T1O$^GrqmcfeA1GsOv z;+NNB)9gim`Z+LlqfYkcS{pBae-12wHv&BQnA@p=av|hvDL~8N&+Wcbyy5KzI zMHI}W`z0YIp%XOUpWpc@bl1nKZHpe~`DJF3T^4ejg6+;%*_fFoYAZCR9i=UViZ~wVJFKzr^M7W|Pr@uw+3IM;1zD z+^|}PY))Z@prCrQ84pmPRg-_Z(CuQU!2}D9+gE5TF;k$d@N|fDO>0}19N{pvc3dpF zjoZtlJ6m|SuEU$6MUj3|r$;wiYh=>hYphwg79D05YaSc;;jc$9lE*6x(eZ2XxYvt^ z9>Vhzbt=?FB7;4dzySJ6-(J_1x&#R7M}?GbywO-<>Fmb%d(F>ZS|H2 zHk+!ZquLJpn;z}?vJXPgu17o*aYJf zkmke~=YfBr>gj66l8xz6vPFXvDdYYj=OV)HXToVpkkv4HWE${JIiyBY7rXIPa-WA=mU$RE0pM%?$)E z`(|Ifg$r|p_6?zW?zg!l7H}w5c6t6chs4^~-WUP}0C@k43mE^inF_lZS~)wKyBLd@ zTN(2k8X7w~O6%L`n;QQ!>L;m4+94Wa{aB}yn73Qw^Wn=`0R%P5`IDh6_$RL#m}%s~ z6oDeQjIn69Z$)KDOM2t+oPRjqo@Ny=5K^mw52K5Ujs$QV_}%pnq0?rg(c%p5v}7cA zWB-1``8m1yd1vAM{#b$mfIUdSYtCx`f-fALKN59?)4_T<5Q5`z3ZD?SKZnd!y)@@% zCr<9hlPTDV@dKC!ktYmgX2Tq0bYl@yoB_4}J@b(VLPv(g2xt_Pjv+)HOc6I=2Zu4O zY5>xXTi}D{lZvoh7){DC<4mM@b>boG>_qfI9H?-TL{D5yDMGVsshJ*U87G%S7v*1t z=8}_-stk$T%u=2%+);tYFCkGnozb4nWVM8$=*0inWD#tFn=FSTO@jGOm}voDDr*mcu%2&&m5z?+Kz&_hX6Zp?h>@0WTo#NiN!Cuo)yy;* z@&3B&&TP1lnuD+Dk}-uA1D{}HB0{v-77qqv8jL(3_vC-zrym(ARrat)&-hC}bT$!a zYVija4-#;1hPi%NA+nPF9PA>VWoGS4eGsu%a`bqUia*1SHnB=O^(XAp3I<0DTi=pn z%OUlhe_3#90|PVAd#>ULdWc42@y0@WB*oWJkh0E^AIW;0yYOn{8FVq@b{#DsRt=kGsk!^t#kmHOiJ-ZI^|>u z*(e=C17Wu{OT2Qh*F`zdWQ4VJVdlw|A97U^POCfL!oVf`ad~HM1;xch6b@qCl5j$W zae46W2H3A+oyH}^aPCQTZJHJDhEi1z%+naylqY9F-q{6ZQ7t@4Y!mN zwe1sKIW2UmH(G5(L19!EZgCU{sxi`QQSD^i+|FO~QUJ#ofp2=R z$rERKS?OSSWBkaK0{yj$<=A1`I>I)|m9moeb;xymV3wwM$Z;URyG6lio4SW-_tKPj zzM!WVOVQ1ss?vtnTUjr&1jux7iqAPj->+x%DQaLn+vJL@?lD-jx;Y6inWl1GazXGK zLI~X?*h1rURkSfKi+K5 z;i2O={6}I%8FvN)S_4(2_Tjjj=2U@n3$S-`fp_-Fe0moiSHg77_E6kg#y$c%dB;8? zIyn!&1hY#WV1XLF0cKBU;dk z(&J_e>L_4R@hjr4m`tXPrX9$_WQL{94fN8DLQ!-Idc3n%u4mkT1uv5@IwEm@!OI)i z{}sHb{-bshw6!rYH+6Q-2C0K2jOn4N%sm*++Xih+X7lhjjYn<7onOnIr$jaEj_>l8;rSGR4LE(&pYfC4doO&Sfs1~tgf3Dykr(?TuwG`)C0&*a+01Cn1#j=8!X=1( zS0WofL!_d9<~PbXZ34DPycH;9xI-ejUSd9dq?}3wn7m0O*8s8>athj^J9U|_=<&r` zZ6aJ|M1twQy%yp=@p<%}jrTi9nq#6?Y8KwqlwH5wA~DIW*sq;&J8V`YJbQE_1xN<| z1LVI?g(4VTun<3VpZl5;v4zkK1t4uzVB+I=j)iGAzzT492@Z3SRs<9IRR z4~4K|@_(er`4t#O9f`%1VdCTYlf@h6!3&A_EF@wZp%qm9Pc8o5>t)hcy!pm~j5roI zzkdCzZ5w$^?!^BE<=lVwJm~&2;`#S_S4`jL@6N(M;ZBr_rlO`Y(l?7Z8$Q-}7n7J~ zVN;-{0<9QvBLxx>G7vFDk=XFbO&#R`MrWKj*_m3D}z|K%x@6(||e{$S&y0ZaiDazElKEf#5w_H6H z83Kilyj^QhN2p_Ov;IOcsg;A+qDu;53L|Ow#Hm z!*f!m!ji_$e(#V2OqrHI)xEvpe>}(6bDP|!>7LA7EVWxwnw}DA0@UrPoATF!Gf|^# zNX?Bvf={S8;U!krMI>OYH#9h^Hu6?&hUZ#PtRoOdW*HmO#apJ3))Ctk&yd-0$qFsi z^3Vy3LcpOGDh&$-9yHP~I)ldyPuG+G^gv_MFQ}L75=hb2O%wVW>3fh?mtYStoH=eS zxT1?SAg)nwIgPVxsO>Bs{FZkf7WRvd|00aGv5Y28;7#HgSGSQCbYBOG5+0;!NS0E; z8AzdFe>y{Wp~uueBRlY9{lYydI07UskI=Gi8~y`BPpEGpvuqN1X6op@pW2<8)O6tC z7n)t7#6^};-WrMuq7n0ww!|QQU4&O{0Ianm9|7rCU81BR(pf>^R|q9IY*Qoe;CFp6 zm{MPCXmv(BT|KTSZ4$K@Z1YPiwb^>&dQ0Zq#CCk1<@AEPTJuKx*g<)S#hiDpeQWu!kv?ZQh(eOPY=->m}3@*c;ln4*p zkzbiheKR$&u)s&e8Uk3LqBFZZgE#JCyvE+!r=oupr~&By@JGX-_0!2~QFRAoi0!rr zE>>L)Fterxe2BUQgc>aZ>e z`h83nSN-C|G_(+=xSX|4Xk;e%E`H)8c z5zaMjUC;?}P1M7>Gd$&%fqcm>fKv2~xT!JP{&C+_tIv`u2zSSEg-()Ao=T?AHEF%c z3sAS@SwzS4LHA$dTai0myUO3(4e+}*?NCmE%_KWK{XucLi^;gQzjDg5OrArIPvIH0mU52d96q8hR&_MK_CzAdI! zJd~@|n1j5(H?*J|Mm{at(Joo0ncEJY6Yy0TVES!05jMIfrH3kyGO$|)|Kr!`CRWw}vcz@41fWI%jp5_; z$7v*AimR!bW{@hR4x!jqz=Y2#RyORez(&zFL3XpK#-gMfb!W;v^t=T}&^$9)A^N;z z5C?MC=I#FT58%I=q`|8><>_B2iSZi%faE`$q@2E!8NZ{Wv9-Z}C)y;HH(ksX_#YZE z4fRTEDnm{^F=Hu2e8BRpVQcCAWXfg)kVMKM83B|=l#9@$`i}ZMRgX658%pl^_80Gj z<+#mR*$2;`(&n8tZOPnFk~jXFDbIA)hpd~)jFzA8nTsDFyWc;Ndt8x%iPa-=y&{qE zi6?Emhw?bnMT3Ze& zPXB(n03bWZ*S}Jhq zWJhH#PV0@4Y2(M~`n2bk!h)Z_UX8a{jIphPH(?S=KT0HB@DDo1H|w7q)@m6Y+dJro zOIgay7v|~?eOC6b%=+wJ9_rGqj4#N2O&V9G1csJ{U7c>JyMA|u+3i_**C2yZPc=G~ z;DKe6VAM^Dcux6&@D~2#0@T(}i%Vv~>(pwiMY7`Qtz)fiY++Kc&5`*Mc z5N74JF}Q@T0zblB=ddf8`4hsGi3>bSwH0tvWH1z z@VO!~wSVW<6~^^0J-A%ROLfzkg_RG6dDHMdV0t)0Ri6=aETcKx*UU{Dfi7HoIos&l zz`rPoE=y?0W1C`&AazhvUMwd{&t%00?V=MNwr6T$Y+$VK*n(?&acQ^<<3ggj^4#Qz zy(XS;e|(%0%}3LfgN*!4&c+F3XSZ0yeV9DnN(W)^RqlS_n#6B}FrBXrYOWv6Uiy{pq~rF1`e{B~0XI0@{K7YhSGr-g2*11D z-h)M?tyDCzB3(hvfpPeLAl@Q@KzE3*?4pEj7d>$zKVm!*I`q{~TJEw;+mdEVldjAPj((~d#Ofb0c;W?viQ=of~)t?IGX}POIFE zLblu;Y+VQh`P&%p9N^_{cBCy4gA$+6j7vYkrf<-S-__omQTAA(;D*;m^&e+%RNlY3 zU+BLfJm^DWZiT?#(nf&(?uK@T64R!~alFG*d7f?@62r#wNLrJ(R6BiIAp^%eZS%8r zCD`0l?Qg;8?CUVeGAJ%IW)dDWWd8*EHecuc!hPZ@T~zB+t{HthgL|znqjvEa9T9B9 z7w_vW;^DwrM?e3?tvWOS6GMuQjwYFEZx&gYuzJwAJt`r)WeJ3Q-nnX81YE24tkG5+&!eOb2c<}J*> zedFB6$1`NJa!c> z_LdIs+{iUP@{;g+I$o$sBSK=STTXLMr835VT3KFvmTc9+yZJeFj*g*C$nZlAX2%jDQI^W-P<#!FY{>tjJQ%naWbE|+IIWtcRIAWApgABYLi ze0Zz`BbNcE<`x9@E@K9itQXPPDxN6;SZh?VFb!juAR8r@vsEqq3OV&f8kX>=_4KRJ+09b3>7_j`n;jJ>ZSRuXKUTcaOiuU$F zAP99VatJVeMzYYiEGK2mu`SdyIWh}7*P#080m{9aYS+Y-M|VEkL^D(K zN}z7PY?WULf;Noin*pj$t^h6eB9OP?b5-^>`cq!t6y92;(kX(T0GjMO`tty+Ph5CI zzN}u`1P`yMc4=6ID<-}=6|>>tNy_c0_^@k<(qGxGk0}eq$ugm5Wo#0MTEe7Z&g}Q*t2DKp#|q)CV<3*&Y<{sE zPWR<6L~hFwB{8|8TTX_`qe7vN9dd9NZ`3cf%A0ZR0mVL4F&P#&g`dUG$IM+EFtfL< z8f&I@KHb&!G1aX_qEnZdb;PX}8p?6O!JfrYd-NyXIF+oNGbBhcYO_b!62Ob$LJ&i5 zFur5 zJ6t|k+3Tt-`ZvGN_VW@%_cPBQ{uZZVAUbCvy>uRl@}*~r+0-?2HRrlp6heKM$D?%% zL$2Rq)M$A-W=|scWo#=;Fd__zbRF2R9s?#o=TZ(TdRz(%R_h)zm^gsmTWMsoB9q$e znHv=99TRcf*pW}#B4(xvUJZ>-jg6#BVD{xg*tEUD9-|Ux@EZ%DV{R1i3|4M2j2<0P zvBrT{@VDye z6?Le&^@HJgsswl`DgY@>}(n zklPRn7^hAxgxn`+&VmFqV=m6)k!*>zd2@+#h(?2G!4FSsyP9#JeqH(GV98-htdTjK z#JfcPO?PCck*+-F2Xm!3f{A5n@UoQ?9!pX-%!aGQxlJXFR+vbUq?%6Z>ToOs!G#Nf z5k++J;>DL&!1wzTxaa-`kifIq^;^uh0|I2c$Q|>6`;JJOvVu+q zWZPRQ2?43)lG=_59ZJ8K^{8W_NMwbmP-m?prZsEz02Lc9ekZS84`+tod!ULn$fXMl zR-!;rzDzL;j5~i!EVH2tLBfm1QL-D)pDAz5u#r3Sc(3g5Q114#ReB@YF1S58 zJTOVJ-P2V5=GqCrdK;9O0%SOt{?Y&V*zow4$QOz zh4+>DoZsMiL&Z9X}|Q+B&BXqnLSP+I7HE%Oq`zm$LuT+EOPa7exfN_h^zc8JxPpsNJj=nnL6CO zZKyc7zFdV;Jb92IO+F!9E;#eLa!By(zIxdOY1GWwC5pv@??@ChDyGaU6j${XGARdX z1oznIa#=8~fhKPDgUGv_i;q|F4T87me&L=4B4;kc|B$Z(T@pO6_XOQ)mbBbHxQ|BB z=Om;(-+mE4`$#gS{FCYioG1@I( zCE?UlXAf2Bn};_sY+XJGOL5k?!ev;=Cr%fkOegs`Ngrh##e{7 zr?%`9IF04wz>=l-{@slNp;?gI9RajX(>4^%L&2_itWC`TK}K{i4Vwkb^D&ipF0~)4 zPnW}hg%uy3?9Rv;`Y3Ch_izRIJ8qo!IH&Ye(FfR&TZXvwJ_9PO{h z=kAH3XU3JFCEHDt?=9mjE>?7^#q1LNDALsW<>(dqs6Mf*NLuGidgbd4m981Pm z!F+9$)BlW+X>5u!`M9@}F>pi+n zlcLIW7tzDn*@0Bn#oC|<%X7aR6gscT(xM<+*sT5v*7PwHsHxYaHrVu}+|DvBivRa7 z?dfA<(l+R{{rK+K=v#Gmi{7T*R?j{Zvnr-i@WVKKy1y^wBn_3vePa-2kce6 zu4cW(<;@c)x4qcvoHVpuupnsb8nEb06PIJMbGi)5xaz8H7QR%t2uA|=nCn0ydhFKA50AEQm}>bUWn%FY56H+YP3y0R zeYZawamCj|hn4JQ7~xU?zs?0v6TCp_0T-fkOv~7x1+%vwQ4*+1iqx2UuHLbAUoNWR zsWJkYeH<59EoM!yF|Nguuj2XR1T)UCy(OWlN%_k>c~Id9lB3!urmLJgKA=O+>UM5fylZ!BoVr5=^2L@$Uq~X7**`4MlNj4yyPz> z=H)#~$34CiV`W@jK(v-2ZnEaf? zG1m4^15VxH5Xm562y!``wBF0f@uPKJaLT~RNIyTR&D-}}P|Mdct$+;J8i#9v!zpNc zIB0X}Gl@i!F)#u!(wIDIoXx~xny{E4r_QyV-3z;NwAA(Cvqra9mW?&_)kc&e?irV3 zQkVT9w5PZ5fo166FHyuzf|ut3J(Fk;PpuwS#qmyuI&zD85n#96kj;$0B8{GOlj+;U zJR@oJymiJVbGyq_<>3Q83P3WW#9~d;!NGf?i=wSzlag>h(!Wnq#V&>nvHG1O=!x+* zJ3S;3RXmR#tB*5PjL?}S&T3e=nJ3;dTP5_IF*^91A(mv?6Q+gp=#$<32Pf_r0#vNe zQCXN*S}VjvLGmqu36M6yvWwrA7kT-3!cd|L_Uj;^n?HSB1?Lg;fs(Quth6+zm|Jux zCMvc8nj<;Df!L@jA6*G%40Y9^+PT&ENK06^kd{B+izB03%9Ed%Px6#ybtRzb$cb|c za>|5n#@h+iWU465iFMoSk-75O;Ao`|>_k}<*G51WfRGhQhF74^IlxIna|mF{?2hU| zCR=Fc)$$>t)BVHTM47H9$Asnq#r=l;J7rw2y97dFn#1lhVB9BN`xo^|BTTGHg^S%LSQ;eeBv|w z%3FVtz;0pKfy#>BrwzA|of)JL_JK9Wm{P9y`Y3*hEH zn)+og>J*j_O3gU>25xA?hCI6l~$bA7BGe#`&%odWZmI*22ty*ZP{bOfc=@EB6K?z=3 zysSxFs%wWz4TgteL#^@i5+C<$`-ZX{!7*5gj7PElRx1ewXufc-U;AmZ< z1rxk7%f@CvK|mj>#`P;dCj`w3;NG^`us4J!2@KDN$0R$dv~yggfxg0oklXkK%N_Ca zWX)D~!#=)Z5fAH->-v8Qwy z_3>#T+`CW(%v*MDoNK+E6IaZq#bK1S!P>utziMMIgR?ZT+rRdk0;D@&I!G-IfEIN9 zrX|3MLb2p6q<<5ICi;TO*#nmaiL^z&h1grk++JI&l0Sx$U1hpW$Y6M*l7>II#Fsa z95llMnSSTES>q={2}=p8g-s6jUGu~ILgf%y90IioE7$z@hP4~^NvF;x&}z~V!w!9X z8#IcJe~RF27sTBsoI@yA4&QJ4UKdE@f-TsKonH}KA<`#4p2G%0-qia(%*&00{hn|q zEBM{E{8BffgIu9xZV=BtXpJ}nABeS&`kydB(IWtZt^l1o2a;YJFm}&)7(KGI{pTzC zAMRl~U?bd25jucKU%Sb>%yn*1HmrYS|&xT)7GyDt2rueXYlQp_VXWQU2XYvi?Vy2;AA_VvyOC_9ziTI z1-&!$>0pi0;1)sw=D&lOY?DZ4HC@z>#)90_X98jsYTG*dqeCpXBAv698z|}^Gj(hR zDjb#xb}j#O*8Ayc-eYZE#i{iz1_=tV-Te?iKO(4gMe4bMl6WGMUosPYrkKMoBIPCj z(S|hXlI{syMTEnNpXF9_B>95+4HuVUI@OfvW1T@MYxA+tu`Rqy#9!+g%VE@W;S{?> ze72VOXtjUj5RC7_VHa~*U@%vxz>_~)lw-hmh8chaKG?Al90fCr44lXZ2=^$V%5aK_ zC%K!=!FPbYTjD=n2RvenTHH~%VA})wHS(Lk0NaUOkN;KunemU78)7zVp9E{vD#1?w z=>`*|2YB8a*QpvL^-SJNEd366(N4fJE}6^^fP^of%@?7WcOb_FF8>*!5}fZeNuK+v z#ZJLae=}$8)c5ZS;-QsQa?r~3zeY>pN})S*P*MS>^NLW_fS@5 z-+2myrihvPjEkA%kF@5&P+ykoBv3+$Q%oH#e_nOZb{6mz0!k*wQw9%ZG@MD;3hQ2Z zb1zPZx)n7)S_^{~a6 zeNxe%YENP*iA&7xOv&H)$JVC4Y8x6dKF)3iTpe%Orw`Akxm;OrZ>BpOHX$qN9J4d% zSF@fWBl+E_xE@v`IQZ^uaJKq{OMlr_)}PG%{2L+r#zQ0J<}dGK=`Zi&|3b(Xu(fq^ zboxtdlGZo3QFPLGaQYw8hq~*63fwo+L^7ceiYXwt7&QLiw1J|8xwsirD^3rKz9I0MlZYWoZ9?RrXgGHOP$qR0EX?;NiHr)oWdtzCMiW6D}j8Ykh;*XN5V zfKHz*gMgdnu>Pc^TC5%aFdogg+8{A{O5FZLJTz{yu~wgQcPHW?R7qh#E6HAaAUXP$ zT9TdMaL1@vYa95NT7n&A=u2zchL?K|t*gJBaU~%oJ}St;NN1!Vnb;~E99sc;IyY%A zYE%^zT!Kk7(25ma*eg8IH+ zk&O)lrTsS3RlIZxu`=U)v&GtEI`S^d3>`b!J6Nf|9& z@uj*}hq!zfF(8i%FHWNC^oNwxF8yN==p{%ss+xw%EIW51_SMwZD`{HyuPKumsY&~Z z2Tk>6bIW4+_*{AN`}8=;GGoGyJ}U4@yGC-^snMa%VU}%^EUpjT^<-Hi{uqP zQyQ&<5#O$E&Gg6A`K+U@d+1@-o@FCEb@+#3M=q3GUtF^eRwfF$Bg^V&e&=$!n z;^q|j(nE(FvsuN6GYN?bMjIWHcUXr^)^t-J9g2091T}!=Y^SsG51xH#+Z}w;WiY9QQ_?B29l6 zKbIdNM zgjC-_-=bPKtk4i{mmo6*oWU|0e_6nQKn`#Tk4L;=`dYmZD)4>QKog+@1wE%CY7yBv zB=kpk5`vjlF$7@;kD4MxmZYaY$^ui?*@Kou&gIF!QeHUjw(-Kn5*Lhu zy78J4RmKeeJWt5dr=~$)RT%h!?iH1pI(94W|8YAtjg*23C3OR%K!d_A-Q6Vw>HpTn z4ezJ@`F=nOVaU^`g_WgK5I&sA>W7Zk%>Dxbm`)-#a^@9|XJ6`g$l{NaiBIR_1pgwP z@0^>$w9~H+v?`m#D@qy{(vlEAAw%%W$#(N9{tf=G?R(Nu+K^!g0DzdkZ3(jf+>-bw z8&ufM*wFdEkAo$thIu0XZQxf?tKZk7#nS5;A^?H~5*c3G1ue1^w?5@*uq+lwH6$-T zBdAlVQ1+V72R2U4bu^j_dgL@pZ=|A7VX)?rHlBI!tnkj)FxsM;6VoR8e1C3dus--W zcBZ*ktb9M*R{*%|?f`OO^cwPaYKy?&!0tk#`(&oz?}=}_ivrw0?`s2c5g(Xy5ffmgTfbYxKVN>1%3^V>~afRb7Y`7$bf#QMpv~{9_9+?*Gic6Dr9BnTHIh}*yLoR<6&52 z%|^qJdW43Fk$`y0QkW^lMrY<+iffeO=5&_ppSK~*Xj!au)|x_Mf}}c+G#VradRlt?LV*E9&~eXvnwsZm>VkdPjD=bTac1mxkpf0D@LW_ zUWg;RN_c}YE-UZ|zO=0+b}k4ok1v%(UlaG1=wId;$UIMFSaK4%V6!Y|=UB1t&+Z74 z>QkcL8lBG@79SwuE@@137GgDLnpB7EAWYhI6}V(CDS~o}?Dg6bNvG0WE-`KL>z@oX z`CWl%Wm!5SR+e^9UdDK3RlgIh6HdOi2S8GeRmE9o>U>cfNUf~m9%6A}4~c+n=|Ids z)0UX*$n~tgzyaERb*-h5#MqQ+VIlg+MLaL$$1ftK-G4u-qRFq)z#$Us@dk7+(kGQv zQ#=_b33dql%5s!nR%Q-p9+`^H5lg@5)Sm>#&n+2NQN~EjJ9@TlRjs$S0S@ez2E<*Y zZZj}Sv0m0{09iNslK=}S{VF4q8JVf2C88tNrKOSX>7x&L(qoOl^Il=D%PSV-(=g>4Nc`1N~h>s z%f+oUw&@YQN=YAKKU#W^!Obl`64G`paR)&LQ^*8{vNEe+eocf~aTp_WHyEkc8FXjp zMQ!h;>}u2aiOdanyL6XKr)C$;1DR{^INCs}5B64YKEWl)A|-tV=@Wt#>5%Vx%Saj- z0dgr_<<>Cy6_PPybMmlJ!d9l9u3(oLvmkf3gsPY;|0LcCKD}zsbn?p`bO7udl+kA_ zQY3~)od1#qDy+2DYBua$7FYBw*|^)q+%x^-d4Rm-`iw$ zcLB=8{#~V;tt)<8-1WVc1E=COz@K+t7VuNOPnQjM9_`m|4b*pV5BM!C=+9sek<)K9 z{kV(0hIVFbAGM688}6J1h4;ehq5+TPg$zw}0rI+KYefeZ%d!)#Jaa1ML;jU(k(rgU z{Qa_QNphLWPiu9CEQ|%mW)Ain602yKYdb3fkCSQ+ zE^7?aH$-8fyllPrGV>_R4+S5bQ$sw$Bcu_RDCQKOR)cq|0KW6aG!XU>Wn|M*pyCy_t zN|%Ce34i{QrXX+mK|pA6vP5q|E7keF%*39%{D}*i<_?+3gsHlw$MbbKFytf+6X^`h zggYcvH|>ExY1Z2d1&K}yvf9kxVFFtsZv+Y3G_qg$})hYWg9fBgCfnK(hSQ>_3U>_6JMzcs;7j z4>cth+Az{L$oT4b!ZkigNI99`z zS&|DjVm$2;Z1J~jiN{4B0tRtu&t$^6Lwkb-HcsjeNDj@+JmEQIsq|J#)vjp_WS!F= z6XpS#;>R7*D_s+lmB&7f_e(u8r|ZTpP-?_zC99Lam%MD2 zrDZWS-0^ez{#IJq6r=$Uhz>wtlHxew%zW_S(e-v4cV5-y;0iJ)B|&FcpGiS)X~N~& zwTxk2P{wW7LcR$hPe!lI1u+`jdM;D&56V4AoJAlQixl&N8#6hplrq6YLeeD%$b5ZN zK4h~S74OkwB6%wvFZUj8o2O8lM++q9z#%-sE-VOCvLqbpiltf+rWV;x60X4TQ@5j| zg*!qW;)j$-sy+Bqv*rryJk{Oy3iEp4ctMlTgHhm>l`#I!0*7K3?Uhp$?-OWnN9KNu zwk(Izybrn0dlqh}IhNcUPi-Ad-N_NqKoCtG`1&Vw*^1l)(jtIriK2b#%co=`^1ao~ zwrR7Rjq57h%u?L7qCk_tQ~lfe2lQXDP)nHJMgHHjk`!ov+@-i(yj|m@r_AaY>;PC8P`rXUGrTpuRR?NRFWZgHN3lL+b`W;-ZvlJBMCq5uk-*J zgDA+Hb}ivkZedzF6e%g>Yz6sZ{t>qhpf$G#Nj{wt*E&`E%&j9ao?mWN{wrmrv1-U} zU0j{ALzuTBptcI~SATXY4M?{M+`E-&Y!fCnls98s$=vw*IKSLdK)N)CpgKkSJe4bl zKa{9O)Inj()hOFGV?vNRcVb{mONYRfjp*=uNRICD+qf=A5^-ZnZx7_#e5Lx>kz)=9 zD0uv1%3slVs`nAy1o}vky(ETMxXShyUL$dHl9+NH4j!Po@pya4U~}R_bmJql?++&8 z=Ttvm%l&J_HLsH=R=!#VzkLQ`Y|CF!x~q0MeY{i=d}W7T?tt4q<%VKz4Uu{KWRX9m zh5&qMaty3w#_cvc2$>c+qT_h+qP}vUHd%e+`G?y&VA#2m=P;t&6u%P z%p7B6{xkEJi^gOMNm(^P_iC$%kf<@uF0c*G&Q1*`FG{TxZxCEu;C0gn^*LZd18e!A zC5?i*dfFc`zSR>rxeZ}eroG4FL(v!`-#)~~VJH|HgY@IjUnfdcQ?LMKYOSzOx>u9uPqvC!g4%Pae+HdBQgN@=w zlwfXRMq+Z);LE0QH{^*(2!JLOm}y+d@1jMYjU@C$v$VR4=+D@uV@98aBAK1@Vh2Y^ z5E<`+Vv74o-a);}7E=><(fyzb=3isRbfY+IK{a~k7Fx9zu|E#cNgXwiMCW)ctTd(O z21$12>;Nx4w`P*z3O6`BE>U_Us-|#U2`(tNCB!X`5L;yo{j&)3)on?A@))IvWU!h+ zbpHsORW6Aye>orXT6#gY5CX3YL%B;FHf6$i|s z6@JDXv8w{tylo6OWXn`O6G$5u^lRI!jcO}10_#hevjBUpf1Q1>VES6}U81L&7?E7yuFhW{%orkzN(y{t(_;VPhUQ&=lCGLJvynfRG3Ch*+{3eJ*>~LKW5KdpSgsA zTr3%bOe|_Gl0AGZ?=W9zYKJ>rGU~|&3_9%5ea?4=M8>DY72hUD#Nnm}E@s2OQZJg! z!o1p87Skj!?NsIq`rqi#+khJJhE?l}3aPCPJzr@ySXCfveM^(l@tBu#Ez>B&<1Pe* zpPA)J!dPji1g3) zOVmn8z?$hdqM*aBvAG${wvN_&Hi&4APd}y*Vw3LY1r(KoDvObeP!z6~7g*?5suhPm zz3<;eASnmCOn8R2jHEQqV5o`pK1A&Yabw?wE-akHnlGw@r=acMKFs4UNx z-J@aE_M&^jK{(W%;nEg8qLA#Qy_;p=SxCc?9*PWbB3!8RJdmv; zYqH>~>8ro2GJP+o^Rh$Pd%~4vqT|(*oH*#rI&s>404IivAixGWdPa$69T2pDQqj!(BW_~0pareVG$EwbbopqKo zywVpnXTx!m#-hkZGptrpq;hV@6DLfYgDq$e;$_r6h>mv}x@9sWZfo`~voK5G7f-vK+_#ncQvc32Oo?(6o2Wh?~ETSn1j;vF&wYi!W+D4z{~%G zb`-}&(@^+HfaH3x$GPVkC`u3SHth;#Ukg#`6?_g_H<)4jfC_u?pyPOiIqx+&-PAC7 zrzPKc%nJ?^G%cK5exU*sRUo`rPC8)#lX@hFY&gDf;xor* zkHpWuzM|EzkA&7-#oxRSB?$pSvZ%(-Lid0~MVf#)aG{U?3v^LxdzZ3;wAcx=BD>ZD z0a$BkX5!4ujAlbQ8jD#M467Cuy9Qf&S+-c)U;2Im4I?0{*Qf<<%@!iq!FKNS8V!-?K)bVc3|HY zaws-HK)n)&cWAq~q0%#>aO48^f%A8K#1N%s)K9iQFYXR~IE2+;@0Eq*#d2Khh$ZPi zKS+)@vC!vJK+^2QI8Z?V^(63ZhQ(I%$ib-AdB`sm<1@)iclYf&e4LB6bDnZbH7wHSX(znr%@EH4O*m*0A_XGPPJ)St9@o{nzFb{dAcvZ zD$*qV0PYm}b@HNd%H4IsV=DHIs$sfkcEswD&K5QPPx_X}`LCg@iF@*AfCMaRH7c<-maJniwiMc%zI+w9c(T>u>o{ZB&N1ic}-5C%ww1|dY~zcB@24H#YJ++QdL z3mb))2zNsuHTw-=^KJ8NjpSl_p7O8z+c5m2<4lWIZBd5$w_9NG&HE6p`&i#0`Ot1` zQKCa$XE40|hV)&vb|ZE}DY7DVDNnKxZyLsZ$V3{ZM6R_!$%$Qca=k`txUASLmh)A1 zWX4!gRSd}@D2c6F%`l%R8$zWgsa`>nifz@c_SFqYx9l@PvUu1=7(VWQd--QHNQ~E2 z-Q^_Gxdkm#IvQ!wWlwsDOtQ|2^5o0WcixLlKQ5))d*?BXU$@M(o88%(DG=g;*29r! z;}!jmKMGLsS*LQgmOC~@Gn%G+4YCT~U>&P{$Ayk2PiE9g2{6uI)u3~i50`hrRhoX? zz^U(IG~hUtGqWGRf5bLW2zC`2wV%GG##BvAt5Em`{hG5!?`MS)PR6oCU7Io)snslE zLapRcS6Sr6S_C< zEPr_P2azwG>zXtT^`25bTgDu=i#ff6(48!MRB*9t&`PyPM$e*W^Q0QM%^D;L>;BZS`eyFTybrVT^0K^^E|MxC;~j7 zgO1Lg3rlN~c{aDR~bxj1zMD{=yaW2ASLp{r{TT>M=G&d-oJB+r69*=Gk( z{Ie7!KBMy^J$l{MB03o{Y_j=>sJRWyaS>aUA#!%~o?njds7I--Ad`YBxdcrl-JDy6 zJH^jZLr2d7LtFg^zSM07lz8#dkt|AT^@d1L_THm7W++u%wm5zh?nOK4Ap2RpNktIC zb2MG1Hi2<p*rZE)_+NDlWCr<5b@$9RAZaSaD zKv-bcT>*3HeuhLI9=2J;!>P{rML<_kh>PZ3xVRCsUGr0E`+JRj1#Qr~-Q;%Z=LXeQ zo)-4R^R6tsGltSF+IvJ`4-npAXq(CumiBZg>pK5}ma4ib3SaN|wgGXPh2zwG@ZKj< zjXx#0MlyZ*2h#Lmyfp<*1ExkD`2J(dCdpm3S=%1#02U^ypYX1vq$Ubs1dms6*3`-- zxgAb-P1DM)Pgz69J~8P@tMEX0_{cHj%WHXXWo>0G98G9>Gev_BB(cp6oTl^=Ge7~# z3S5HP7$$?4&S~dn8ygYqAf*dyj~S6 z|6x9+-UAOE{9063G0II(2QH!co$tzs5rp-jf{SRZs{Ps0jh*tRQiHUCH5|pE!C40jq@yq!-Ju&W?+~14N_o{QgpKpj41+hEuT+Qu zNblfzE3;QP@95~8>2>(%Ap{}j9Vxd&|6*~6$H4Gx&-Q+j&zEOf?~3<+g3#L6kw?u6 zQZ!okbcZ4eE(bbXm%}Sr#_ty^{6K?O?uy)9lLC5nh~>gc{8Rmprc`qR04d@d5ReK8 z5D@$StmOQ+rc@Fs8v{K{Au~XMfSJD2|HYjoDrib#16Xa7#v2Qc<#vrttC|gNAr@z= zyPA^x$e@G`foS-i6jE`7GHokx@zUX65Ahqm{Dhw~oWIiB!}(s*zv3*UNrLU*8(Al$;~7 zVx?a8JoTN2$>JM;VYHhMhA4B-rtDNj9A{qY%kU|kx(-$ zQSrffNSFSB0!Qu@SwtSmogUra%d?0;MzgA(d~7s_cStM@*d~xJtRnR*bTf1*YaFFP z_SRgEefc77&r)!@JG>0z9@1pNB>z>PYdvyCl7YCw+5#lZ4T-4B(~V;c@|^Ne%kS#q z6Ma6YAuhBU%E#7Tm-ro8xqkGPnYH3Bd*_Bv@uw-bEucK}XQ?6eD!dIc!b`@{ITucg zC!MG!vD`hj%)NVnz`Zf(Q^XlO8g+20{P?`lJOVW#f9MY*V*_fm7yrnJBm?4n>jpeM zqYBhJY0oL4BZ`bq;wMXa&E9QyT`4hFPx9qXDBf0(^X*U`)fJlOi~daXcjPwU|E}r9 z8AxDb0`i-Z2tYuD|Fb3hcP3$=YN!v238uGkeLE8uEC(908bwSIoaH4EbX>zcNsRLv za}PC?wwzrZ*9!Ho^pW>s%CUjlO@IUuCfxhMx~18JNi5N{89SG zIg-ja-AmNd+vc7}_L0ZYSfWq14_LSJyP}anU=0Yz%sL&GrqLdSt@6H|)L>b*S;hb4(N zW0GglN|X(@NE0aoqCfN&%MkY~73eXE^Yu(^nMikW(^r!wDMQi^I9H8m6BUKU7*BBG zV;N%wcTKg7J)2NidA;>avBFeYsbItCd28 zM(oyu)GO8%3yC?GTv^Qa`ZKXN-=QYPtPP4RmW#5CxZSwgd#~A9uf~u;f zl97<4Ni2k3qb?SkjyX_*3BK6pjvT1$$Yd5Oa}!O%oTfWBT@JT{Yu@9*3S!99Q(|^8 z$Oz4J+0gQlkrM=^+bhQM4l*Gg2**~(E5#|0Fsl>wCUyvrSAlcg^JkvqFhYFW`?Epu4eO$&anjP#H@yqm?)VpwJ z$yIsK2<}ghjnTVIAL_eKAHEPhem8#VTf}x)=2WYQ#9%gaN6->!1!W(PI$2e~uszx; zx>IqdMC~sjL6*{AgV`+aU^c_g&>yoeY$F%2$q7rpsQ2JV<*NtS%`h)01u73WveaFC~pEhkjHBC&4916a@HM)HW$m zs+em@-mhw4hb~sDCr(Sec8o~nsZ(kovsT#3D^9PTR@bC3uqh6HxS`!b)2^LvD|}%u z4udKyi$a~1F)C@YF3ls=qpj)SA_yTwI}VsrIOuk@G;pRig8`4-tx9Mn%)XySd@t9U zJU#8qo>_#-myr76V8~bD5rNkIJUYsPMO2KZwJBA>%Urr>5vxdLHW%YLzd*xx5~**( zTZc87nQYllA8+W_C-MdFvZjzVrWq>fRM&}#(4VYBcf`|k$t1?X`=yR?y1}$Q{IuuX zwXqor#Hz~%&VjevJ{_#d@u$+f3qtS4YloehmqCZ`^x@m(O1PKh5W7R`(v-K?6LP_2 zR{gcpQr4j^uxw zCUQ%(Kzv`Pv6OLWUf!D5>@EXt@ZaF+lvL}S)k2tfuwOq)wV*3YK$s2?KY!9<-sw!q zGtMyJBZXkk(s3sH2&dGZNGzWXV8%G6%(0_){U#j>V=6OkF^1gxJy!Rd1-P)t68tEV zPYVkX`ZU@NdW|*xbY1039%D&>b7|~PAxd2@eUsrQ60#{mh3+8o=~r&nAR7wZIWS9^ zfM)944~6tqEO)i7!lqISyFG#98%5I#Z=|kkX|Q$A>F-$W^Iajc(^ynFclSP7k1EY* zH8jXAu%yTo1gkEX8(v_JnS;{)8Xr#T6`~E2Ca&{9GPi+1pW64Y`b78y*!@0Iyo^k~ zE_$fW`ozw$->=9d+FKciXAoO$v17OT0yd*S-E!l}fBJVPqJQ0TFX8*>X= za|<$OlLFDn?Qt7bD>w(%Em3&**EK^00nvy?mtSKT+s3>{#7#ZTMoZCM6=w^Ax@NQ^ zFohu=1Yh%xDt}YKJS;a#sZP>;+@awWjEaHBb%nw=tnkjdkRB%*=}H6j+)hxV7R#ww zN)`KZZAn+^B46)wCR!hJp2olYu1&B`*QV?G)6xDp$@sv?QkGe+oG|P9ssH6=a|eqb8zO?Nye7=+fuq4PaLp|GN` z--+-z+ow2+J+eGbbDIN}dccRB;gnT2LBxEO5!)1Bzzr-yB_b)Cyl7b!$vXIG9qdv9 zG!o&_(^o)gsIRO1-3wp;@GJHLr+?uA{0SVu^%q9`Ux0ENmw%D-X#Rs6ZVTX^(AxeV zvNj+>n39mDrEHR>laLw_Uyz<0*{7nK_%Sjr-3a!#PVqN41aTsxGJQwDV}k(~AR7s! z?__3aNMmngU}R?N__t@WgfPJO5x@eubSae9)n-ipF4Rn>zJP$mK&2#+C-Cx_%WclM z?3F*fr&88TZgYcS_Z1Wo0PpAy4YjB&v+|={c7uCo30(QEkEJRA`SMdI@dL0%^QVq#HXs< zs|hp5XcLesff1R*hfe?Ftc+i;`e5~ILA|T>vf@>3yG*U(nfMY0CF?R=;PQzC(+>;l(YEpq@!k*yWQ< zi3+E2{@z0U^#{pMf#WSCLdl6-7V&m0brDvT7N9qN859@ONC;i59}Q$f-_(S|&Nn2* z(x~$%E9JBD-b7T0+h1T}qtQdMP$Y;=0~PE7mNy}9uI8YB81lP8Rm^!4mndNz$xu<+ zWNy}Ux68@~1T+GM*T#hV-zmo zNvwdlcIaN=P9AZ=mzek|2Z*Q1G1wMxgeN$LNA_#vJKO_Js^>rU69oY%+!DaDdjg2Q z-2cAp{{6p7n>jd`S)0h({uK=K+nWF?<{gdxv)Ca~TXs$tW$0^)wXO2ZFo&Rv5j~-k zz#zoem&}ijL58_U*H0CpB9&!BaTaZhuH$A9`-4D7ERXo67hyY?F{_xy0b6n~iR^+y zcIqW_so_81VmSe*s0{nc{qiC4%%ltDRLChwCc=~xLJZggEZ_sHPH>V!3`6wy%kkN^ zYcm&c$?cr}k3S(dbeLNAj^X>XR_e+J$|imk>8vwE?xrc1+sRX63p{<0Mg2^o91SCc zeM0LKXu|(#9Zy(itW1&3Z`RVKy0&;x?73DDzf;%PHz93}t$+Yed8GRb0fl(+~e0!ciqlrVhyp{=2-(6SG=0@>8 zjmYstL`Nb9S=3%{j||PEo(LZ02CYy##~JZHrC`$M-XX* zD1XJv=VORoSuz^a_~Yi!AgL#3dMP{ucJF+HAcq#gGPY}N#biC>Iv%=+(?Lp-u67YyC2+&Tny zag+Qm4w+a`**Gy=|Z5geHbU9E*4kleFY!OT?)7;KPL7wJ5x#ENx?8#OoG&} z-?q3Qfu)=YS5_^uc(fPTthOUS`K}X=)oj&()O<7<>aZy=inK z#p?*GPcezIfM5!lvXh!3y?p~iwkNoYN`u7#^FVj~9C_>gIfNyQ}036)^8itXnGzGxmqI?>+8R=Q&-sbBz`f23K z8B!96NrV)HLe(ODhYj5p^s52Hsrp*U8S*!E#FAVs9|T(%Zr$$^hQwv7CdVrc9jV_>+}dB%Nbec;Yq}e z)Pg6dzhp;UZ3(m04B4y>=yq7S7TRbUPot6U#e*rXO6x?+vTS_ljCLeGiUAw+r?T!Mid+Wgq8(6VSy<*760FXhU^x_KH? z^$_AnBrIIQKukJ&dl`sp3t0aG!VG#e>OhE1USadWcWj+!nZ8q%hdEc5l82 zQ)2HxWzs5Fc9AptzHFgPjjL{;|A-d-*n0aP@3U-S!j1R>e?4=xhAHEYuc|lQeBO_^8 zw8lbG*d!?uh~sJz#3Q#aAKs>&86yhz*f%T1CCZ8n`BNYjVQHUxjr&UUK})}U`trbJ zrCY2XM-wN6Ovh`zPxLZ+x{3!{r_y57k`kSo-c6KK#z@!(z8~~&L*y{ha z#V5v2NPsY)1j@cL|cthB~o8!o@n8)um*GCq=6kQ(4{X@wP z$vHX*G*FVm7*shM#yNd}xCq=4#jNmf%vVIPtYzd#pD^<}V7ot=>Rv#22)3MXw*>hB1A)MtyJ%IUbMJ{iV?v__O)Ww&ZXYnl2S3L_akVoa9JA)y z=PsrAbg&M9GyBF%!{99J=A4&!|0YWR^;Y=MO}~a906smS)#87(14&u~1`+*h8~T?A^0z~H zL(Re!bj<72ffKZe>^Um>4_Pr*G7R^1U6U18|D# zT@G)Pmjho}KHq+FZ6?-&xm4wl66Sw5K$gNJRErS5y>-*E)WOlwDv}k)Krj&KMZ#R# zE`bGeVYm;Z?^63sw=*W?*etdCr+3YR#8Y|D-IFK6!^pDFixB`UxgBXX1dtN-dar_R zcm~&h{l40R=y;dwjedS+$LAy1!@x_pHo$bM>3xRsA$N15h{(Qu(!-42Hj#R}gMJ5o zl6)pDcT?)E1}M~W6#(Y&p|1t@VMsuHz)Espu2r?!sk5wr1I`AL=|%l{>>`q8IQjje zTCeFv?cg9Y)22zvtM`PnV>?;8Pw>yyYX0q0KwCJskTz1fD4On#Qhz;0XyLdWi)ylM zSc}(pa16p}h4n>hcUC7Y$%5ykM6cjRyGoV=tWZE(h24qefG>l-x%DVn4+~6`%j;W! zaa05N)1|)MWy#KbgZAfP7noG%96emK0CHin&Q%7UVw%i4CSBlK> zQ6e?D4wzIVvU|0nwm9n*C7A3U=I_jn zqOxexjeJa2Cjp1~0;@=DJD#ddy%lpyqy-venIG)_TU0GzY(HGl1feJO#d;IEoAPM4 zEZE_V60Y*nMWO^K2MSAa`b-Kd={R=(EtLYLg z;0xv=ZTT|CO7Yk;@?4=4GcS%(9i&l;7|p#yDL^`;gH@KR)zHCF<#s z6qfWiMxXhHEaN(1`qhwsbCT34S)sQ>PmgV^aht)nk33WkS$yY8$9i?eZWMd1I2S0q z*$(t|V$xY2ve*!d3{Q5zr+Um{>(b-Zq$VBfr=ULfJdm+~X0*YS6 zz^B5V-nUuH9WS%XoR=$iKgpW*y0~D}==uN?bj}x&3p^o8J>MeJJrs#Neel8=j({K& zIo7~i(>as^(>s*jnbT<$6`^vdAK5n~n?h%aF{b_8-_*IosBSP=!{S>cG6X7JaUyr2 z?vbR)N7Z;A_3^j)EnPw(Y7YwW`kR8eLn`Tr&mQ*_F|kRbyZAUG!-8wf;74r@jgH+a zuxKN*0-0^$RmXE~2L{b5Wbj3AqoHVRG6vF+VPgTrET-n=VLU`xeq`DBhvHiG4E|(S zw9efM7k{U&$8osV8#CA#D_{s)2i-kHb=f^ub8$&mEamtTW3PHOa{ACj2Q|L&$qidh z)d#AsF5R*_xdDeP&H;3k5&+==T!NFkFvs*+B5Qn@(xk(cGMq1C=MSk>fvYc$xD1-n z`LOuBk@~SmWn0dY%JnA>>#GLa!;2+;-i#9#{-f_ypiF^{w-G0>Dr!_W^~QIPbmJQ& z6;0vp1wZ3zi&yOQVtI$t51$u(bGjV=oH&PN?qE)dp;xg!<~(XEtjJh0d}9H>BK{7V z&n_o4D|Kkr0@Q}5JL!G!1!G&E#&0uELVsxq$>sL&r6Pu@O7UId*_t8Jd`Kh@74qSaUXbKJ4`x3U;b1B zL$P=M-HtOD&+6;o@=#@>G?2B@btLfm4Yj4MoJ!iPAa=(5Dfl-Hmp3Pj>ZRL=3(eO# zYI1teyZnKa)Bezkw!x};u#vj+XAnWfJM_G#r0N_^I~Y}g5z%u`-w8jl&oPX=psb7O zsQT1ox2k`CnQ;XT3Ecjj5BZmefMbDHI|1<7)&NmD+y6dB`Db*JsB9%WCx_x~y)+}w ziD9F74JHJOZDZt10E?8NkA_a4N_b;{IYE7*G3(r)y@Rk5{;OL||M@(cC~J+?p+;gy z&|`|{h-0etsiVQC%KHOct~)A%`OxtGRu$oplzJGkmcjsP3|U7)EjD)d4Mj&>ZSUF% zN*D?oS%=Bd3L|O9ijlk1x`KZdMx`{0XZB$BaD9++0Pw(mhIV zA^dkFfnqD`-eym%&Rtk0mNzs2^ypMJJxBuvr3C-%SgZa6#chG?3fS3IE{!`s z@e8-{1heS18W#ITeU-$#)toIet;^uLY1la+`)D4T@mTd5TobtoQ{`$InLlYQ{Rg(y z_PZkTCKbgFuG7JU0E6W~5VcvAP7?su3^&C-!(|XXK!6gl&C};Z39E4t^MRjz+Cdt>ZysX)r{4@H#1f1L#6Y!>lSMgE#ovAM~65{pGHNb0A?{ zB9N~hH)!@xD*5C0%;C6(s__g$yKgrzT%xz+ZM1|Jlg=fJ126^8T^`m#-2R@cVT<9Q z=nNFonV>z@+fd@`cN|&z5uU}z`g_vQt`l zCP`UL6veTsI0(L#x@J;{9CwA{aRIQ;7=is34bXa%YL1h2-*SXgNP2Nrz7M}Wn~gu8 zQR7W>^1DeXQr0D`pf?c36Gck!)yco|m#PgM{|$iu*9zI!Um)KBtPpE}AIprR9L8tq7hi9yF{Y6cWfAxCYA8( z`j?g%YBUwPx9`{X;8JfSHd|Xw2Tv+Ak^rgQ&f(_e+EYfC*X6|i$5rzc(7v4}KkObf zC;be6c?Nxa@BTnff}h#AkR3~y1+4wbUKZW}j^I0z%UD}G88GZA$lBtDQF!v0d#axP zfL&z9&TU@d5p+_jrn3a8HM**lX7#Sf>GmBg;UyOANTSI**p&J@tGz{*#VR=N08Fr2 z&`$n1uWW5pHbE@d9BZdAIFDCGEeF5HfXO0e@0d(%*clpSdE#u*CGTN+60OcYN=xIU zw&Jq@linhU<#{pJel+};p_S_TWdIS=P|Fk0aE{lz`i!@a z%NY|xlhHRQ}ncF^;Py;k`wReNb zU1nvsP;O*>OJh5piuZp+phWH?8gT&qD;4hFSpEM{y#Ez-{-@rnqUrD#A0+`}tX3Eq zwtokYz}MjWIvQ|7fgEJ>Pch#DalstnT4hnCSS|I#*|*LQn2!6(gF=J`#omH($Jc&A zlUMRr!BuZj6~mP}$)fns$*hH}4I7s~Jh%8hU$5A{$v0LwT=b*{oKdV&PP$y1$K9~T zf%iqORCq7++^J&0wb zc8e$ok|N@R9>|8}`^QP@Nz*LeqMhZ3R8iLZMa(8@0z(Np%*w_37RZl_e{f5!;TEV5 zi*PjA!u!bG1mrLDjl`KUPasI~RuOBkSmy0h$y)u$zz zl2ijnD$LX8B|^@OyXt;sE{m~2wwY=s&Q@GfOR%p)uGWRO=2fD>(j>Fpua`776r=^( zZOoHx3|k}5AZ^TN#v?1707Wo})-QkwV&kR6B4Rc|r%_-kXJPP*I&5Eh!-DW!uyZGEE(ghC5!hqB2jY zGoLY{3~VKa5J1sTEx$x$sV`5H{n$dRM}bQ;*{yME&+RA^V<9d2HfO=82+W>pPkzUT zO>$YZ;CWVx-PmY9plhrtU^AuUn4bd$?l|j`S)YGWQ_Yeqg}i9iS91wg+f)p}j;Hyd zstPGahpEv`(#5H#!QX4l)_mPhIsdCQ^yO|=#2Yl8u2j$4^G^X6 z16f1Ql5Jwoari~8=rf}xu7$ic=tsRjezMo4ejoy`u-V}k==Ti2ECjZ6@#z{hp=U94 zcaAJvaGieXEA^;8YxJ-YId6qiDF=Jn??ffJXeo?W>^lD%SL5`+Pt9s~kK%#;1%|BO z=3-Vm?bL~&Wjs=ol#O-kA<#Zmf;6Xn8e9k+8oab5?~AeEmt(+b`2!MHS(0?3phtJk z%#6ocUXUr=ucx9XCE(&@=BqA>Lq(aC2n`yC5Z)oCGCxTVF+QgNW^6I3q8-cm?ylW` z>y;wT&qz1F#Uj5;8gXLl=`K6N_5fsaw8}vmn)cOM-FuJ}*)8Ul(A-;;i%5&kC`(|J zTX1b%v4M}DnIZZ!v z1i7rS-yDs-M4DAa3d4f?2;_8ki9 z$CjUQcULaEj4ab8$k@VNdMQqzNARXt9}qhun>wpT7@OvSvIt0fR0fy(X)oPZg0-oq zy`A-AF!A)Vs*w)WKeY!rw8-5KZDaok(1DFsyFm@uhC3f=0cQ+>(KRae=r{+LG4<%k zG#wYFQ0<%(y=XW&_x^BZbBM5)=?cbi3lMvYh7(GMo^M~PekwflUT((McuuuCJ+i;s zkIUUZOkJNq8^OJflYEoHy8StlI{dvrVA6Un6|0;0&2`WV53HDOh22Xd{sg3o8CSdY zre@RLk$m05aMmHu4OJY9yrZS*)*M;i7;KGVv8qI-svDUOKYpPWSXts_Sz&WP6Wb(r z3+p!|7&B+Q$;|en ztT7&!&-afH*lomLo`y9ieFH_oaluwW=cP)s84QMH9#-JZNKc@GU6hF}nD<-)TX!-- zsRPFA2lD9_W>(kZijS2#6L|G($6hjkg!Tcp|bjbW{ zae%>RPpzjby!maTT(O*eo)r}Hha#{Ot?)bvn1`G9rOHoal7CPi41_iOyX1m)@>V_f zx7-lzP{C>P3!%>xe@q7VYTfKBCyslHVap#Vl0;nB^Z^BJoEl#AwQU42RWK-h21`e3 z-28MIC~T0V?ApUwhH^*&OhpacF@060N72!4yWkF^g?n+rO2!zC7uBPXCTb;h@1;FY z4m2i5&!MKl3?id^&0`@NCfox8M38;mAarMM3$0Pk7FQj0-f5y zh}v7=IAUPs&pY{E1=#~9Wz@p2UP@k?M4eZ8&JkEMScU^g*X(>*p;$fOGi2#m2BwA@!J~1 z&QrMKSJWQrjkmIC2bqjFOK9~@otn2ckf-3_nO#ThPlT@2{&ZK#V^2x$E*d{v@ z3*(hV>3n-bx5XyM{Nc>f@Y6U>wZ@0p?FJ3J*lEUcbhw2ojkJLH$X}uxM&c}C{8q7R3%N6B+)Hw>IV)o$N~i7rJ)GDsskQ5 zuW$^S%x=;ZoSz**5@R-~HX{1&C(l=0$;7zy>5dbDHpo0rM~x#N9|?j;9GPcpw5sIo z*nj%mUtWebM1UF@NSX)_?l!6acz(3}C5N0KsXVth7};A;3X|s_kMGE+auZ{uS^{}l z?%ZlFd2G&UTTqq^ohDXUp&E6f5~wlD5xbIe4gAB=ajfn4XA^Zvq6W{!FssG=O!^|& zLV4?)b#W{K9yVK&1$ld&6Bw6XlEi99Uo(4Wry*K#18L>{lZj|eU4Yhhurx3}WD_RN zYb%@(;B0q?XhdasI}U1%t5TNzFkD$`XcY%qn-}Qe=gva)qM^PWn5PT$ zmdDw2nZnNAy}AQx&zt-^IvNwdd*D3yiNDfzY4ontGj;6%1<`58o^evT&lHIs+rH$g8QKZnt$0LN7lS&!o#2Q1 z?x#9Mrs(e?%$vWR{EQkbQtd|x82AYE^1-5F^e)mv&QQGF{ERE=wh^IQjIy9GQJ$}Q z$Pyi#StXmgnI&N}NPi*4-`;%;^Cmn&Z z(bwkESgVAUR&+Tk$#!M0X8$@KqY05&iOY~7yhra6N+RT!c{Y{R1TJ_v6=4O34QHW3 z8p%HQX4{0>;YL2~zYI2~p%lu1GTkKWO+WlPk(V@6%S5C@ngVj_Pe(VC; zPXK9&kX8WOL2%+bmf5Y5DyI!_qTtpX3=&bK7NnTM^sQiy#ato(UJdX80URA>Cz^dh2qsKz3JLA_K=WPQcSoB|yffjrft=aWc>$Lb59`v)%!TkPMfBs=o#C z%eTq`J_2%D}C9CHGA`-qb<*={Casb^UsmZ?xzLs##`p-u^u36K2I@KED};dBgP9aNbZ@38&JgE1NY{6(@h6NOl8iT?Wmo!h z5eTH2Z)h)p6E&N6iZej4~DEY-bZX z(kQ<3>6=_TPL;e0XJ1&SgMLJF|BCxnz1r|y@3(Z3eAiEMbR2RfTHQ=RT0Y9A3e!%+ zgS(Wc6fDOSki0x`)+V9SD$+c>9Bkp=vXLSi6nGDHC6I_Bu|^MCTQS{?l5Yyyz^GgN zV8TQE^gtVe%pIVY*4suR3EG>fre4epHJ3J{P#RJhRR3RNR{@pPwsjHd?r!OjZs~53 zPNloMyGy#eq`SMjyIUGW8WjG|cfG%gzWeSO;~NLZaL>7W@3Z#WbLCG)rPC1I*xPeX ziCX7C_U)pm;)-`KgxnMx+{2Dtz4kD(zq^bsDZ+1LDEC(nT%wSzQ@;r42Kl<{yk}0& z)kSzqz2X#J*M6RIPkVE6yh-jhPmu@W(xxaW&^ja#o=qe`0&a8W@vFN^2p_YlD_~Ox z4cOFi{BG!aZEaz!r(+9vSps|`jr44OTH>ELOr}Oj$aM0e_>F;r2)gpT?#eo92f;$N z+j=1zN|i;7aV@|ZM{gDY^BnR~T#5AMmuC;;TPTI}^MYH{C;KVvYZvx;7N@jjKvxxN zylB`?rXMR}MJNJ}aqJ-$kP)HWghc@tgMB6C8dJ)bkqF!Hz%)wDRpwYnRV6rv+jPVQ z&*z8t(l8LhRo^((<|iE5ES>qSD1P?hTog^GqPfYS@bUCBuQrkMf1zV-C#igSV_@hy zHOKGo8)jT`*)BYMrLwnxTOzoZxHlTHM=~dQvrH0$JPQ_%bQbOxjzbynHt54n3(w_j zAO|^7z$>psUu_TZnXoHJbllRC`C!}6`iGj764&)JxKL{~d9ca~tDmqGTW~|OmyPJ~ z=so&PU^_cJ;KD4~d{Q02RV&umEUuflxCMTN3gpM9_`J@dCK!M6tA=}_W z=b`04%ML+yg&d++kJz|SJ+K0!aTAz&yC)8ulqNJ3v}X*Qlqf_6`Qg@qtl;vA3lf8= zQmr_^cnJb9!3h7}rav{|_l>%MmW>`DAe5fDjghU9z22XFk#gn!a)@PgrC!&Lti4g` z367&}%DvMj2ou-lCpPAvx_$9O7upLFxi^-2Wulp0$S8Vp$=!DV-} zVRw|v;cB;%(vXCQQvjgwsMogQ$C&z&2rcB@9Q@g3$x|uvv$Qae5{S?D!-W1Ka z5!8N(F&w@nT4n~l79aDe@z7bvMShaaw@~Vk}uwS58A+VBywa&G*^KAL?uH#Kl5G%m^vK3pehq93`2Pr>i+(r00(4bCs2Pk9quKv zh{0WsR=YN@XkG2Cu-sVI1ud_?txOm$qGSzHNt=cp7WvZMi?=2X_b;*rFO~~f-&@4s znQIj+w@Ncab}%D@8z!)UP$V{q>uDpafu+$me_5k{tDVl;U0zf8!hhw`nBG)4;^X{r zDDGTzBX`$TFnA7ll4b^G@Zp{qk`FiQU=}Q7rlJGw4nbMDCn(410kuFJk!bF<3jf*#F*~P=N(;A1>FEiFF5^r+70srm_uN)>@SIsQ!zxUA$T3s8rdc!)&7@f{|GWoE!mjbPKH7N$ z*4%+@1)X}YjjKADBD;)!+`VDGDEnJ(^gUO?viGY(SZ`BA4jpqN4w=p0pHLz;EcTfQ zo=Uhblef(oyB0_*L2TKn6SQ1z276urW4-;jMY=Etma6KMeZg|;Sf#vcom%$^l_R-% zrf(z*^2^irjaI)MQxvZ5ZNayq|&o$12hOLgEhPGV?k6oqkV=%I;f0%+7H z8YnQ}TM`NCB)NwP%XX1y5-iX)SdARDsuMN*5VBM+M)VC+F<}Q!yE8af@qDr5xV0>5 z4Fp}q#LIleiD-FT-VaPsNF;oWHN`RQnKYFR_K-;8wd^;+yMgS0GX+W=a$r>(@a)F> z7@s3s;z?TBwGqS^(+TN8KY}@lH3g;zU+-9A3C#d@qUkjrOuVH;eVWng>8p=C(zz(g zUps^X$KGMoUtS$3f6q5dD+z00j%+oT*g_9p$6=z%2o`>Ot2&A=GzC~wDO8cLMJ)h(SlV=1p9#vLQ9uzx ziqD3{)YbZqB!dK%8W_t*xtn34WFlCngFW#@h=0Ia=lpl0x%5@A(iizId9_u@h~@^U zNNa(*1Q&av)jh$a(fxR+H9!_6gQx?MUzlnX!E~|;Oqyn3=jV<58wsX${I+CoTb9j3 z7$TjLu;LUVOY1>0vil;Zfql_h<3koY z#AUhYiWsU&y>?W3DusT>I{TX1V3}T6q-x2LzXZ|6Bx;hf64#d%2%hUOz zd-9YA`ZiUlAyUblHl$M*QJH)hD4@KfCyCFgc83OS{Hv^APekcf@G5VUxDUT6q{iUx z;_LCVK-@d#=eG#86-t)vf((zq?9O_FfnkfjVm8j#IF&&=ZU(l(=fDsq^I3BS_4FvX zD=%(AD`cF_d>p=hD5I+x8Q=Le_cag#IE!N@rb!>E)h6d2IkbaO^U}I`>tsg2K7HP1 zX1EXEQDiUHTfI+st5h&NFVc$=3jWL09u}LW=4`ok?POo=m zUCei=V8hgi@-tr9!Fvp>yWDd7oT3Z7YInf+LcpW@smry0opy=~jHffg*tL7T45H2y z9Bz=s2Y;)K^f?i78;N^s>c)DF?2xxlqCRYuCw9GbkX;*TdLOL2cU#)B0q}I!Qc0Y= zeSFM+=NDKy_jLl?y`Gr zUPL4%!p|3L6A4k8G;rC9<^0%;X>Bo#^#)c}mwzBH*M-GSQvn-ztp!`IC7338NF3HZ8nIcSWe3U)Q+)2my4%NWk=y-!+&8pe zoA<+eO2YZzA+M~OEi;P8I6f$-@Hmgtc=$AaG{{67`0RUFO@JuNo}6>b*zQl8FA54>96l=hhN zEt?}s{jzy4R`H6}Wrk1V1g;{FaC{60>e?-?{?O>HvL#fxek7)I|6)tEW(_}pyc0Skt6--X_(!V2 zZI-wY_fnRE;ZgfwuKafC=gDLtY2p&(aYn2=MU^#i_%8C#+ADVK#nVtZc)S;Cg%*Ll$}q$AXy`+q_g_ zu9p`1Jo++Ex$ng@$hMnlli;i~zAqg2VLc(GxZGD%1MSd!&JZxq4)C;pB9Mu5{nGd&}gS=)dO7dZAugbikk=ps0G5e&*nZQYr-SIrWsqrR)`6 zFG4l1WiZvHEvrMPv0YbB-)5x@Oa3wfkua&<<1Jf6Vh4{5ve)T58)~UgcP=C99MBd# zu2Suj6xc7ZmFp->D`I}yeIYc{hWlt0sr1#SkS4~3TlRsifok&_Ajq&7ej5MDguY>- z%TR^KDJeXvF1}jxGgk@5a!6TtoFPS6j?F&z1x#|vNj`WWN)b46F-x?_gf#VGu6p@Y zvNOdgM#DIB(%?!9(et$y@5$; z_6O^s!HB7TJgiIk;1Z?%I?6Bw^I(OST>KH*4-j{i$7Sn}T^AS)z6FN_7h}({9e8$F zFXi~;7OJ)nvib8gIkMx0=dR^sp0C)n}?T;Djm=UI&3V9EHc zO$iv_ZIXRS>xATDIz!mGp2{Ir;b_=^SPR+M#N#+b2m{2YdA>=(#Z=RKczrd_gmCn% z!&d0^{`EHPNoG}13yU5ixjsb6$GI;zJFWaBG#y#cR>)X4=wv!6?ljYP156;CThrmngT9 z4-qN^*H=|p0UyDM)6^-` z!ruU`g)xP*OvraT(r8GdPoSwvD7_EzSTdrrlVjA7qOnC*5v@=ZRKezwIKDsv-EXQ6 za|bKDCKtqi1D={i7m)!Gkt>}h%2}U~r7mujCZe${%IWmtc++fpB-NJWG`Hvm=y*fK zh?XaOtpA|u;se!6FaFdqd(;EcJ(p;?xo(%zzRAt1 zSoFS?GjNN@dsBuyKE|#zzn3hjU`BF#hZn^AI0Bq9Rb?AS+lp3s zdAD5~Kk6$i$aWp6WImhP%%Y_3S_UIqS z@4n)7pV9)mRResYSL&^?1{H;i+10Pv7f3r)LM9B_&9NuT2DL8WvQ8r7iZt zpY0mMUT2+fK>orEY4y%UNQBq}AWhKz8R-tqa9N&rY9pPjyo))*F;TN)UYob{W;mCP zxWWdxrLcS|px*;_bxh1@5YI^f^Gxcl)(mlu^3(IA&uU+*s|%XrM?qa@ffeJKpMf4a z?>GAgqqJU4SSMtqj|S|&{9vU-ZZZwjg>=P6(c;V3%#sZ4Hv&~ID;gNuMn15uO{Wqx zJZt*0;b9ph-fmKYxB4Z)n`3v+yc;)Zj46@JclN%d@RBkZuiOq~+seu}*h;)nK9sA@ zw~9LjUu#W5&An?s9=kkrvkj8iGaw?^&M=JO3bEIO1j}6^-Y2P^3V!7+Gt*~eRs+64 zDDe`vIft=EH&r<^Vzy{U{3g+>b7-0NwOll?;dEmdS2ZHGDuU>V0dnmdkntsT$A%UP z88D}4&W;I9KuCArjktU{LPru{30_ik$RiP`c>bmZ(e)V!Xk;U7;iMD(B=NWv_!4P? ziwDqWS2n&A_Ym=GgudMT2@p-WNNA8x83g-0>y~H1)jl5O?@y8%;)!h@_ z1$l&#Zf;fXAek&DOk2ShSB1@wjzI4U#2Lks*bU9NJmMzLcYzetW@+j4&mCX%Y;on} zzN8%ry4#I6%mmk%mQiX?)NgSSyEWOSQLm~jj;PH;CuQQ2O_+8h#mid;8vXL|@)`S) zYR)cj zn}4PHihcD_gy-2E8>L5>U5@im0w3-D#XvDAHl9;k=Utp6Q6P^HLz9prnn z06pC~vcbdWWO4O{D?%qh@DruHkuLUOB7{X`vLQG31vLfbG?74Qy<5|(5`6M(QQJ#V z=Sx+)5pP7PrzQk8x*(H8Sw`Ghf$n?VRhNl#4QTCV!BK8t%|ZES@a1GG`eTQz_2?(!NBpl7TJ-P&3`dvw z(JwVm^InvT{ zww3piv4kxLY9O?tU3d0X`XTwvAEWf@H|HAEb~+=SbtS>oq(cZjcJKB;7ouIMj}AD_g*RhR(OtPsgC))U#$iwWHa!4B@-Qtf*7WR%3wgY>E0un1}V+8$X#zqrFl8#4SNpxISp zC>uX1n8bfa@Q(4cX1DF!Ic0TTOOBz}4wdz@a<7zsgU%&E*O66iy4Kmv3Lh(*lM-fL zqx41jIVH(0z3bl0;bW%OX30(2K0vq`dzj|%L7KoZwrS~#5Z{YZ{Gw-=zxJ{Gh$8AP zqo4c55RehPn4ID8zA1dLxhtP>ygaDS1)gBA;_P_e!FYln@PhEt3Hc@nf;iI99(zzE zM5AE##hW+yoW(HPB+J3{F>nHeLj~{Y{i_hS5KA)l$X!M58ZteE#r5Z}_kqeWfhEl5 z;K~u6<=Tc5`)!}sV`QERGn+&iy9x=f)*tcY;M=?*1A*I9Aw`Esn7a{}Qvz4ZVfr*?m!Wzrjfgf)N#gbskO33xd z@M1S?d*aI}GNFGI1?clBfWw4;)#v}}?th&jeD?y8JC^?D{X7L<8&jh(7*C$$t*}U= zN3ls3*o%ey;u$gw*dy$*a-69{@=DKM_6^8GtRTTeH~6Q_P=`D!{w0tbo847Tn-i|x z(cx1b9`|P-HWvs=Gh#?}@*??E{B0=YCldm4wFqHh^^6K9sq-wA(ljP5-*!FsXS+^@ zX{h0Ph*X1fNS@W-TQavv)M_^gsNIdK(r&V^AEZ+|;+jjQFrz0n))b)AoikM`KCQF& zeT+M0y^_b z3y)zqg@@63NQm7$I~jK$j{hW}gOhVv5983LA@}-X(5Z=L8EoR%A%hInD6in-*p~mR zuk|orXECH=dc`!Qr4wg!2E)dav2zWRv)D>h&M~a2TmyaC9U$y8GIXHgGOpQuL8j>Y zKadZ-OZj{Y2ZLM>MlMsUH5eVHy**_nXvX~kLz%wqMWh6t);e^aJO2{5u(-cZj6pRH z;aAk?M;8B4Q&-LnCIXWRtsa5X=`csSTa>Icv=VDtBRsxSu!wYEGR}7b!6PE;uu&qN znTb0MI^A%M>q*|psV~SF$LVlKc)LQAye`Z$J?kSo&6fAIgf|-#jSLc`iYDoYDb>1j z8lx~)PPo*Cuvm@!BJZGoJ%Ml}-IRX^i0X(14Ftsb`?UVIR?NRS1O;gEIbbQEJix(7 zG9-TV&SWMn5raVmhApWzqG1xBntnGRR1joDW$y`@h@x+)A1L_fb6UFN^7atgOkF}L z{VVPRoL#yXfo^%OO6R8f)q=sPg~xr0+s#(lTMuwcP##gXfF+_hl9V3Y)nd{55E+tU zqLKXcvk5Lp%wjR+zFq{Dvs;8#-Z<84@K3oQ@U>v&T)tMWJ!G8CP6V5TYmcJcb41oK z4>@@zS4cjrI1Abcaba15bWszwb}fnnMIYTr-ja$D=%B=Wj?*@FT}6VrO4FxTAH&e6 z&}4|!RtZBNRDBg&XDUZApPVPFAf+Z(qL=+f_JWAD$#f5#SbhYgOIeIdkz@J8Vp1k! zXuyj^w;kS~c+?h@vBkW+cu~8~TxXFQ)RJN}%sl5}6;L@76&z}eyHdr%L=biqZphl_ zi+S3rz9GmPWgI&G3v-9jwG-Btl*gnDlW5RVP#ES-<7}iMxJ^h>492yJ;bjyvg4^9G z3`)%8kknFQ&RKZo6gx?cZ_1Ji_1ND7x6EG{+H~6n6fltpR>0zg&cpN`AckS%`pBCZ zB;uz;?~U5yr*s{hZ8Xf5B-wV^O2pN-#X9qu5uu#H>aiBZ+VTH;()36D^Ja9~dBH1t4s^ID2J zVt!xEVR=3iXpdrK6nV_JGFlZx$fH3OX zAYDgUI}Ij_G0b}_&r{uLtN!Fu%u(AOsu$i)9A5d9YA9FObjPzNHMaZSsB}&`;5O-2-;C0#T(OR_;J$i2ZC6)yQPK8G|3|XG(!Zdtr>(x5xGHL)ZAzDgdac7v<+Xg ze~N(Uwcx5b9jBJ&T9LZ>nC|nH|2aHq!6j!WL0lSJRBVj-hdC1<;F@R<+u+t+M4||s z4%;bJWG_ajiHOKm2!#x~WBq2w_h{D-WKP#|=^@r_urMqHxLSc)UndcD{nN|&eHdYA zHyS7;A-p!ggwfpi*2XYGCTuStbTmzQdWc7w`QASFf+XBS#$)}YXtIBbUSY7^ahTD{ z8{<6>frt#<-5Jm=5d7&Fp;E**M5J6W5Dg*MB6Qr(5;flNkEtX z=K+^EDox^N8Ya755@=%9slC8r=Ibv4+LE&=KF4Mt?!JqqwoaaOWx7Jzf(1#x95#zq zDL7W)uiR1Tq~TxL@0I)JlmjuX5-rVfC|bfs*eb4b@%&RF}5q&<2!# zqkFt})d00XnR9q~=ng|Jv3MtvrV1a^+j)5ewVK12WhF#3j|pQ_n_bi;7K*5nd1ifc z29bUnj8G>|@0e|>TAe-rt^?9Jlf3b_41GJ73QZI56gA$MF>z_B$-gwRw2;GkO_xBM z5-*4+jUbr97vqPQq}~O%Y?qU)!F(n zp+HY2rMiaXDpT6Jt}DlIm3#I_2I_u(IZ8ZZN06vjSe;@{r4tNX6HHE?wveiwI*qSZ zq?u#R1iR!Y46h!0o&5E5T;k_G1jLVq`=10-t%A0w<;VI{iBznz-x0*xiWt4q@PT?9 zXf5j0FkxzOlblQ*828EY8hAPB;3&l$C) zWj+2Lr-zgtRn<`#(MTzp_*`T!Y~9X*>f?J-_7|KImEOf)!+_60%UbU*2biDq zE=m*tdsSHkt~!9*wS5I@ru#a$Hew?R6mx$*6cRl#Y|=DShezG9DtcYh$CKFzku%6I zTkukXVZ_{?@Omj~ajKI^LYwKMqr-_dc@7^>9==?D1^09+CVSrv3(HaY*@!+≺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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1d64bef --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Nov 11 18:57:22 PST 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-bin.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..9aa616c --- /dev/null +++ b/gradlew @@ -0,0 +1,169 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/library/.gitignore b/library/.gitignore new file mode 100644 index 0000000..cba8ae6 --- /dev/null +++ b/library/.gitignore @@ -0,0 +1,8 @@ +.gradle +.DS_Store +.idea +build/ +local.properties +localhost/ +obj/ +*.iml diff --git a/library/build.gradle b/library/build.gradle new file mode 100644 index 0000000..1435a90 --- /dev/null +++ b/library/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.library' + +dependencies { + compile project(':library:libs:proxy') + provided "com.android.support:support-compat:${rootProject.ext.supportLibVersion}" + + testCompile "junit:junit:${rootProject.ext.junitVersion}" + testCompile "org.mockito:mockito-core:${rootProject.ext.mockitoCoreVersion}" + testCompile("org.robolectric:robolectric:${rootProject.ext.robolectricVersion}") { + exclude group: 'commons-logging', module: 'commons-logging' + exclude group: 'org.apache.httpcomponents', module: 'httpclient' + } + + androidTestCompile "junit:junit:${rootProject.ext.junitVersion}" +} + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + } +} + +apply from: rootProject.file('release.gradle') diff --git a/library/libs/android-support-v4-text/.gitignore b/library/libs/android-support-v4-text/.gitignore new file mode 100644 index 0000000..cba8ae6 --- /dev/null +++ b/library/libs/android-support-v4-text/.gitignore @@ -0,0 +1,8 @@ +.gradle +.DS_Store +.idea +build/ +local.properties +localhost/ +obj/ +*.iml diff --git a/library/libs/android-support-v4-text/build.gradle b/library/libs/android-support-v4-text/build.gradle new file mode 100644 index 0000000..77c147b --- /dev/null +++ b/library/libs/android-support-v4-text/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'java' + +sourceCompatibility = 1.7 +targetCompatibility = 1.7 + +jar { + baseName = 'android-support-v4-text' + version = '1.0' +} + diff --git a/library/libs/android-support-v4-text/src/main/java/android/support/v4/text/TextDirectionHeuristicCompat.java b/library/libs/android-support-v4-text/src/main/java/android/support/v4/text/TextDirectionHeuristicCompat.java new file mode 100644 index 0000000..e0a54f7 --- /dev/null +++ b/library/libs/android-support-v4-text/src/main/java/android/support/v4/text/TextDirectionHeuristicCompat.java @@ -0,0 +1,29 @@ +/** + * Portions copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/* + * 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.support.v4.text; + +public interface TextDirectionHeuristicCompat { +} diff --git a/library/libs/android-support-v4-text/src/main/java/android/support/v4/text/TextDirectionHeuristicsCompat.java b/library/libs/android-support-v4-text/src/main/java/android/support/v4/text/TextDirectionHeuristicsCompat.java new file mode 100644 index 0000000..661d8e2 --- /dev/null +++ b/library/libs/android-support-v4-text/src/main/java/android/support/v4/text/TextDirectionHeuristicsCompat.java @@ -0,0 +1,35 @@ +/** + * Portions copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/* + * 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.support.v4.text; + +public final class TextDirectionHeuristicsCompat { + public static final TextDirectionHeuristicCompat ANYRTL_LTR = null; + public static final TextDirectionHeuristicCompat FIRSTSTRONG_LTR = null; + public static final TextDirectionHeuristicCompat FIRSTSTRONG_RTL = null; + public static final TextDirectionHeuristicCompat LOCALE = null; + public static final TextDirectionHeuristicCompat LTR = null; + public static final TextDirectionHeuristicCompat RTL = null; +} diff --git a/library/libs/android-text/.gitignore b/library/libs/android-text/.gitignore new file mode 100644 index 0000000..cba8ae6 --- /dev/null +++ b/library/libs/android-text/.gitignore @@ -0,0 +1,8 @@ +.gradle +.DS_Store +.idea +build/ +local.properties +localhost/ +obj/ +*.iml diff --git a/library/libs/android-text/build.gradle b/library/libs/android-text/build.gradle new file mode 100644 index 0000000..1ca9842 --- /dev/null +++ b/library/libs/android-text/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'java' + +sourceCompatibility = 1.7 +targetCompatibility = 1.7 + +jar { + baseName = 'android-text' + version = '1.0' +} + diff --git a/library/libs/android-text/src/main/java/android/text/Layout.java b/library/libs/android-text/src/main/java/android/text/Layout.java new file mode 100644 index 0000000..5b1f78d --- /dev/null +++ b/library/libs/android-text/src/main/java/android/text/Layout.java @@ -0,0 +1,36 @@ +/** + * Portions copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/* + * 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; + +public class Layout { + public enum Alignment { + ALIGN_NORMAL, + ALIGN_OPPOSITE, + ALIGN_CENTER, + ALIGN_LEFT, + ALIGN_RIGHT, + } +} diff --git a/library/libs/android-text/src/main/java/android/text/StaticLayout.java b/library/libs/android-text/src/main/java/android/text/StaticLayout.java new file mode 100644 index 0000000..426c371 --- /dev/null +++ b/library/libs/android-text/src/main/java/android/text/StaticLayout.java @@ -0,0 +1,37 @@ +/** + * Portions copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/* + * 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; + +public class StaticLayout { + public StaticLayout(CharSequence source, int bufstart, int bufend, + TextPaint paint, int outerwidth, + Layout.Alignment align, TextDirectionHeuristic textDir, + float spacingmult, float spacingadd, + boolean includepad, + TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) { + throw new RuntimeException("Stub!"); + } +} diff --git a/library/libs/android-text/src/main/java/android/text/TextDirectionHeuristic.java b/library/libs/android-text/src/main/java/android/text/TextDirectionHeuristic.java new file mode 100644 index 0000000..8e08636 --- /dev/null +++ b/library/libs/android-text/src/main/java/android/text/TextDirectionHeuristic.java @@ -0,0 +1,29 @@ +/** + * Portions copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/* + * 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; + +public interface TextDirectionHeuristic { +} diff --git a/library/libs/android-text/src/main/java/android/text/TextDirectionHeuristics.java b/library/libs/android-text/src/main/java/android/text/TextDirectionHeuristics.java new file mode 100644 index 0000000..b3de179 --- /dev/null +++ b/library/libs/android-text/src/main/java/android/text/TextDirectionHeuristics.java @@ -0,0 +1,35 @@ +/** + * Portions copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/* + * 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; + +public class TextDirectionHeuristics { + public static final TextDirectionHeuristic LTR = null; + public static final TextDirectionHeuristic FIRSTSTRONG_LTR = null; + public static final TextDirectionHeuristic RTL = null; + public static final TextDirectionHeuristic FIRSTSTRONG_RTL = null; + public static final TextDirectionHeuristic ANYRTL_LTR = null; + public static final TextDirectionHeuristic LOCALE = null; +} diff --git a/library/libs/android-text/src/main/java/android/text/TextPaint.java b/library/libs/android-text/src/main/java/android/text/TextPaint.java new file mode 100644 index 0000000..82a1f05 --- /dev/null +++ b/library/libs/android-text/src/main/java/android/text/TextPaint.java @@ -0,0 +1,29 @@ +/** + * Portions copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/* + * 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; + +public class TextPaint { +} diff --git a/library/libs/android-text/src/main/java/android/text/TextUtils.java b/library/libs/android-text/src/main/java/android/text/TextUtils.java new file mode 100644 index 0000000..a75e7e4 --- /dev/null +++ b/library/libs/android-text/src/main/java/android/text/TextUtils.java @@ -0,0 +1,36 @@ +/** + * Portions copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/* + * 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; + +public class TextUtils { + public enum TruncateAt { + START, + MIDDLE, + END, + MARQUEE, + END_SMALL + } +} diff --git a/library/libs/proxy/.gitignore b/library/libs/proxy/.gitignore new file mode 100644 index 0000000..cba8ae6 --- /dev/null +++ b/library/libs/proxy/.gitignore @@ -0,0 +1,8 @@ +.gradle +.DS_Store +.idea +build/ +local.properties +localhost/ +obj/ +*.iml diff --git a/library/libs/proxy/build.gradle b/library/libs/proxy/build.gradle new file mode 100644 index 0000000..0d6d727 --- /dev/null +++ b/library/libs/proxy/build.gradle @@ -0,0 +1,32 @@ +apply plugin: 'java' + +dependencies { + compileOnly project(':library:libs:android-text') + compileOnly project(':library:libs:android-support-v4-text') +} + +sourceCompatibility = 1.7 +targetCompatibility = 1.7 + +jar { + baseName = 'staticlayout-proxy' + version = '1.0' +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource + exclude '**/BUCK' +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives sourcesJar + archives javadocJar +} + +apply from: rootProject.file('release-sonatype.gradle') diff --git a/library/libs/proxy/gradle.properties b/library/libs/proxy/gradle.properties new file mode 100644 index 0000000..d7390bd --- /dev/null +++ b/library/libs/proxy/gradle.properties @@ -0,0 +1,7 @@ +GROUP=com.facebook.fbui.textlayoutbuilder +VERSION_NAME=1.0 + +POM_NAME=StaticLayoutProxy +POM_DESCRIPTION=Helper to access hidden StaticLayout constructor in Android +POM_ARTIFACT_ID=staticlayout-proxy +POM_PACKAGING=jar diff --git a/library/libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy/StaticLayoutProxy.java b/library/libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy/StaticLayoutProxy.java new file mode 100644 index 0000000..6a5a164 --- /dev/null +++ b/library/libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy/StaticLayoutProxy.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.fbui.textlayoutbuilder.proxy; + +import java.lang.CharSequence; + +import android.support.v4.text.TextDirectionHeuristicCompat; +import android.support.v4.text.TextDirectionHeuristicsCompat; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.Layout; +import android.text.TextUtils; +import android.text.TextDirectionHeuristic; +import android.text.TextDirectionHeuristics; + +public class StaticLayoutProxy { + public static StaticLayout create( + CharSequence text, + int start, + int end, + TextPaint paint, + int width, + Layout.Alignment alignment, + float spacingMult, + float spacingAdd, + boolean includePadding, + TextUtils.TruncateAt ellipsize, + int ellipsisWidth, + int maxLines, + TextDirectionHeuristicCompat textDirection) { + return new StaticLayout( + text, + start, + end, + paint, + width, + alignment, + fromTextDirectionHeuristicCompat(textDirection), + spacingMult, + spacingAdd, + includePadding, + ellipsize, + ellipsisWidth, + maxLines); + } + + private static TextDirectionHeuristic fromTextDirectionHeuristicCompat( + TextDirectionHeuristicCompat textDirection) { + if (textDirection == TextDirectionHeuristicsCompat.LTR) { + return TextDirectionHeuristics.LTR; + } else if (textDirection == TextDirectionHeuristicsCompat.RTL) { + return TextDirectionHeuristics.RTL; + } else if (textDirection == TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR) { + return TextDirectionHeuristics.FIRSTSTRONG_LTR; + } else if (textDirection == TextDirectionHeuristicsCompat.FIRSTSTRONG_RTL) { + return TextDirectionHeuristics.FIRSTSTRONG_RTL; + } else if (textDirection == TextDirectionHeuristicsCompat.ANYRTL_LTR) { + return TextDirectionHeuristics.ANYRTL_LTR; + } else if (textDirection == TextDirectionHeuristicsCompat.LOCALE) { + return TextDirectionHeuristics.LOCALE; + } else { + return TextDirectionHeuristics.FIRSTSTRONG_LTR; + } + } +} diff --git a/library/src/javadoc/stylesheet.css b/library/src/javadoc/stylesheet.css new file mode 100644 index 0000000..96e35e7 --- /dev/null +++ b/library/src/javadoc/stylesheet.css @@ -0,0 +1,571 @@ +/* Javadoc style sheet */ +/* +Overall document style +*/ + +body { + background-color: #ffffff; + color: #4B4F56; + font-family: -apple-system, Arial, Helvetica, sans-serif; + font-size: 14px; + margin: 0; +} +a:link, a:visited { + text-decoration: none; + color: #34255D; +} +a:hover, a:focus { + text-decoration: none; + color: #6A51B2; +} +a:active { + text-decoration: none; + color: #34255D; +} +a[name] { + color: #1D2129; +} +a[name]:hover { + text-decoration: none; + color: #1D2129; +} +pre { + font-family: Consolas, Menlo, Monaco, monospace; + font-size: 14px; +} +h1 { + font-size: 20px; +} +h2 { + font-size: 18px; +} +h3 { + font-size: 16px; + font-style: italic; +} +h4 { + font-size: 13px; +} +h5 { + font-size: 12px; +} +h6 { + font-size: 11px; +} +ul { + list-style-type: disc; +} +code, tt { + font-family: Consolas, Menlo, Monaco, monospace; + font-size: 14px; + padding-top: 4px; + margin-top: 8px; + line-height: 1.4em; +} +dt code { + font-family: Consolas, Menlo, Monaco, monospace; + font-size: 14px; + padding-top: 4px; +} +table tr td dt code { + font-family: Consolas, Menlo, Monaco, monospace; + font-size: 14px; + vertical-align: top; + padding-top: 4px; +} +sup { + font-size: 8px; +} +/* +Document title and Copyright styles +*/ +.clear { + clear: both; + height: 0px; + overflow: hidden; +} +.aboutLanguage { + float: right; + padding: 0px 21px; + font-size: 11px; + z-index: 200; + margin-top: -9px; +} +.legalCopy { + margin-left: .5em; +} +.bar a, .bar a:link, .bar a:visited, .bar a:active { + color: #FFFFFF; + text-decoration: none; +} +.bar a:hover, .bar a:focus { + color: #9D87D2; +} +.tab { + background-color: #0066FF; + color: #ffffff; + padding: 8px; + width: 5em; + font-weight: bold; +} +/* +Navigation bar styles +*/ +.bar { + background-color: #6A51B2; + color: #FFFFFF; + padding: .8em .5em .4em .8em; + height: auto;/*height: 1.8em;*/ + font-size: 11px; + margin: 0; +} +.topNav { + background-color: #6A51B2; + color: #FFFFFF; + float: left; + padding: 0; + width: 100%; + clear: right; + height: 2.8em; + padding-top: 10px; + overflow: hidden; + font-size: 12px; +} +.bottomNav { + margin-top: 10px; + background-color: #6A51B2; + color: #FFFFFF; + float: left; + padding: 0; + width: 100%; + clear: right; + height: 2.8em; + padding-top: 10px; + overflow: hidden; + font-size: 12px; +} +.subNav { + background-color: #E9EBEE; + float: left; + width: 100%; + overflow: hidden; + font-size: 12px; +} +.subNav div { + clear: left; + float: left; + padding: 0 0 5px 6px; + text-transform: uppercase; +} +ul.navList, ul.subNavList { + float: left; + margin: 0 25px 0 0; + padding: 0; +} +ul.navList li{ + list-style: none; + float: left; + padding: 5px 6px; + text-transform: uppercase; +} +ul.subNavList li{ + list-style: none; + float: left; +} +.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited { + color: #FFFFFF; + text-decoration: none; + text-transform: uppercase; +} +.topNav a:hover, .bottomNav a:hover { + text-decoration: none; + color: #9D87D2; + text-transform: uppercase; +} +.navBarCell1Rev { + background-color: #DDD5F0; + color: #253441; + margin: auto 5px; +} +.skipNav { + position: absolute; + top: auto; + left: -9999px; + overflow: hidden; +} +/* +Page header and footer styles +*/ +.header, .footer { + clear: both; + margin: 0 20px; + padding: 5px 0 0 0; +} +.indexHeader { + margin: 10px; + position: relative; +} +.indexHeader span{ + margin-right: 15px; +} +.indexHeader h1 { + font-size: 13px; +} +.title { + color: #1D2129; + margin: 10px 0; +} +.subTitle { + margin: 5px 0 0 0; +} +.header ul { + margin: 0 0 15px 0; + padding: 0; +} +.footer ul { + margin: 20px 0 5px 0; +} +.header ul li, .footer ul li { + list-style: none; + font-size: 13px; +} +/* +Heading styles +*/ +div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { + background-color: #E9EBEE; + border: 1px solid #E9EBEE; + margin: 0 0 6px -8px; + padding: 7px 5px; +} +ul.blockList ul.blockList ul.blockList li.blockList h3 { + background-color: #E9EBEE; + border: 1px solid #E9EBEE; + margin: 0 0 6px -8px; + padding: 7px 5px; +} +ul.blockList ul.blockList li.blockList h3 { + padding: 0; + margin: 15px 0; +} +ul.blockList li.blockList h2 { + padding: 0px 0 20px 0; +} +/* +Page layout container styles +*/ +.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer { + clear: both; + padding: 10px 20px; + position: relative; +} +.indexContainer { + margin: 10px; + position: relative; + font-size: 12px; +} +.indexContainer h2 { + font-size: 13px; + padding: 0 0 3px 0; +} +.indexContainer ul { + margin: 0; + padding: 0; +} +.indexContainer ul li { + list-style: none; + padding-top: 2px; +} +.contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { + font-size: 12px; + font-weight: bold; + margin: 10px 0 0 0; + color: #1D2129; +} +.contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { + margin: 5px 0 10px 0px; + font-size: 14px; + font-family: Consolas, Menlo, Monaco, monospace; +} +.serializedFormContainer dl.nameValue dt { + margin-left: 1px; + font-size: 1.1em; + display: inline; + font-weight: bold; +} +.serializedFormContainer dl.nameValue dd { + margin: 0 0 0 1px; + font-size: 1.1em; + display: inline; +} +/* +List styles +*/ +ul.horizontal li { + display: inline; + font-size: 0.9em; +} +ul.inheritance { + margin: 0; + padding: 0; +} +ul.inheritance li { + display: inline; + list-style: none; +} +ul.inheritance li ul.inheritance { + margin-left: 15px; + padding-left: 15px; + padding-top: 1px; +} +ul.blockList, ul.blockListLast { + margin: 10px 0 10px 0; + padding: 0; +} +ul.blockList li.blockList, ul.blockListLast li.blockList { + list-style: none; + margin-bottom: 15px; + line-height: 1.4; +} +ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { + padding: 0px 20px 5px 10px; + border: 1px solid #ededed; + background-color: #f8f8f8; +} +ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList { + padding: 0 0 5px 8px; + background-color: #ffffff; + border: none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { + margin-left: 0; + padding-left: 0; + padding-bottom: 15px; + border: none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { + list-style: none; + border-bottom: none; + padding-bottom: 0; +} +table tr td dl, table tr td dl dt, table tr td dl dd { + margin-top: 0; + margin-bottom: 1px; +} +/* +Table styles +*/ +.overviewSummary, .memberSummary, .typeSummary, .useSummary, .constantsSummary, .deprecatedSummary { + width: 100%; + border-left: 1px solid #E9EBEE; + border-right: 1px solid #E9EBEE; + border-bottom: 1px solid #E9EBEE; +} +.overviewSummary, .memberSummary { + padding: 0px; +} +.overviewSummary caption, .memberSummary caption, .typeSummary caption, +.useSummary caption, .constantsSummary caption, .deprecatedSummary caption { + position: relative; + text-align: left; + background-repeat: no-repeat; + color: #4B4F56; + font-weight: bold; + clear: none; + overflow: hidden; + padding: 0px; + padding-top: 10px; + padding-left: 1px; + margin: 0px; + white-space: pre; +} +.overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link, +.useSummary caption a:link, .constantsSummary caption a:link, .deprecatedSummary caption a:link, +.overviewSummary caption a:hover, .memberSummary caption a:hover, .typeSummary caption a:hover, +.useSummary caption a:hover, .constantsSummary caption a:hover, .deprecatedSummary caption a:hover, +.overviewSummary caption a:active, .memberSummary caption a:active, .typeSummary caption a:active, +.useSummary caption a:active, .constantsSummary caption a:active, .deprecatedSummary caption a:active, +.overviewSummary caption a:visited, .memberSummary caption a:visited, .typeSummary caption a:visited, +.useSummary caption a:visited, .constantsSummary caption a:visited, .deprecatedSummary caption a:visited { + color: #FFFFFF; +} +.overviewSummary caption span, .memberSummary caption span, .typeSummary caption span, +.useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span { + white-space: nowrap; + padding-top: 5px; + padding-left: 12px; + padding-right: 12px; + padding-bottom: 7px; + display: inline-block; + float: left; + background-color: #DDD5F0; + border: none; + height: 16px; +} +.memberSummary caption span.activeTableTab span { + white-space: nowrap; + padding-top: 5px; + padding-left: 12px; + padding-right: 12px; + margin-right: 3px; + display: inline-block; + float: left; + background-color: #DDD5F0; + height: 16px; +} +.memberSummary caption span.tableTab span { + white-space: nowrap; + padding-top: 5px; + padding-left: 12px; + padding-right: 12px; + margin-right: 3px; + display: inline-block; + float: left; + background-color: #6A51B2; + height: 16px; +} +.memberSummary caption span.tableTab, .memberSummary caption span.activeTableTab { + padding-top: 0px; + padding-left: 0px; + padding-right: 0px; + background-image: none; + float: none; + display: inline; +} +.overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd, +.useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd { + display: none; + width: 5px; + position: relative; + float: left; + background-color: #DDD5F0; +} +.memberSummary .activeTableTab .tabEnd { + display: none; + width: 5px; + margin-right: 3px; + position: relative; + float: left; + background-color: #DDD5F0; +} +.memberSummary .tableTab .tabEnd { + display: none; + width: 5px; + margin-right: 3px; + position: relative; + background-color: #6A51B2; + float: left; + +} +.overviewSummary td, .memberSummary td, .typeSummary td, +.useSummary td, .constantsSummary td, .deprecatedSummary td { + text-align: left; + padding: 0px 0px 12px 10px; +} +th.colOne, th.colFirst, th.colLast, .useSummary th, .constantsSummary th, +td.colOne, td.colFirst, td.colLast, .useSummary td, .constantsSummary td{ + vertical-align: top; + padding-right: 0px; + padding-top: 8px; + padding-bottom: 3px; +} +th.colFirst, th.colLast, th.colOne, .constantsSummary th { + background: #E9EBEE; + text-align: left; + padding: 8px 3px 3px 7px; +} +td.colFirst, th.colFirst { + white-space: nowrap; + font-size: 13px; +} +td.colLast, th.colLast { + font-size: 13px; +} +td.colOne, th.colOne { + font-size: 13px; +} +.overviewSummary td.colFirst, .overviewSummary th.colFirst, +.useSummary td.colFirst, .useSummary th.colFirst, +.overviewSummary td.colOne, .overviewSummary th.colOne, +.memberSummary td.colFirst, .memberSummary th.colFirst, +.memberSummary td.colOne, .memberSummary th.colOne, +.typeSummary td.colFirst{ + width: 25%; + vertical-align: top; +} +td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover { + font-weight: bold; +} +.tableSubHeadingColor { + background-color: #EEEEFF; +} +.altColor { + background-color: #FFFFFF; +} +.rowColor { + background-color: #F6F7F9; +} +/* +Content styles +*/ +.description pre { + margin-top: 0; +} +.deprecatedContent { + margin: 0; + padding: 10px 0; +} +.docSummary { + padding: 0; +} + +ul.blockList ul.blockList ul.blockList li.blockList h3 { + font-style: normal; +} + +div.block { + font-size: 14px; +} + +td.colLast div { + padding-top: 0px; +} + + +td.colLast a { + padding-bottom: 3px; +} +/* +Formatting effect styles +*/ +.sourceLineNo { + color: green; + padding: 0 30px 0 0; +} +h1.hidden { + visibility: hidden; + overflow: hidden; + font-size: 10px; +} +.block { + display: block; + margin: 3px 10px 2px 0px; + color: #4B4F56; +} +.deprecatedLabel, .descfrmTypeLabel, .memberNameLabel, .memberNameLink, +.overrideSpecifyLabel, .packageHierarchyLabel, .paramLabel, .returnLabel, +.seeLabel, .simpleTagLabel, .throwsLabel, .typeNameLabel, .typeNameLink { + font-weight: bold; +} +.deprecationComment, .emphasizedPhrase, .interfaceName { + font-style: italic; +} + +div.block div.block span.deprecationComment, div.block div.block span.emphasizedPhrase, +div.block div.block span.interfaceName { + font-style: normal; +} + +div.contentContainer ul.blockList li.blockList h2{ + padding-bottom: 0px; +} diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0f878ae --- /dev/null +++ b/library/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + diff --git a/library/src/main/java/com/facebook/fbui/textlayoutbuilder/GlyphWarmer.java b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/GlyphWarmer.java new file mode 100644 index 0000000..b6b0d10 --- /dev/null +++ b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/GlyphWarmer.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.fbui.textlayoutbuilder; + +import android.text.Layout; + +/** + * Specifies an interface that a class has to implement + * to warm the text {@link Layout} in the background. + * This approach helps in drawing text in post Android 4.0 devices. + */ +public interface GlyphWarmer { + /** + * Warms the text layout. + * + * @param layout The layout + */ + public void warmLayout(Layout layout); +} diff --git a/library/src/main/java/com/facebook/fbui/textlayoutbuilder/ResourceTextLayoutHelper.java b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/ResourceTextLayoutHelper.java new file mode 100644 index 0000000..55cd831 --- /dev/null +++ b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/ResourceTextLayoutHelper.java @@ -0,0 +1,214 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.fbui.textlayoutbuilder; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.Typeface; +import android.support.annotation.AttrRes; +import android.support.annotation.StyleRes; +import android.text.TextUtils; +import android.util.AttributeSet; + +/** + * An utility class to update a {@link TextLayoutBuilder} from an Android resource. + */ +public class ResourceTextLayoutHelper { + + // Font size in pixels. + private static final int DEFAULT_TEXT_SIZE_PX = 15; + + /** + * Sets the values for a TextLayoutBuilder from a style resource. + * + * @param builder The TextLayoutBuilder + * @param context The Context to use for resolving the attributes + * @param styleRes The style resource identifier + */ + public static void updateFromStyleResource( + TextLayoutBuilder builder, + Context context, + @StyleRes int styleRes) { + updateFromStyleResource(builder, context, 0, styleRes); + } + + /** + * Sets the values for a TextLayoutBuilder from a style resource or a themed attribute. + * + * @param builder The TextLayoutBuilder + * @param context The Context to use for resolving the attributes + * @param styleAttr The themed style attribute + * @param styleRes The style resource identifier + */ + public static void updateFromStyleResource( + TextLayoutBuilder builder, + Context context, + @AttrRes int styleAttr, + @StyleRes int styleRes) { + updateFromStyleResource(builder, context, null, styleAttr, styleRes); + } + + /** + * Sets the values for a TextLayoutBuilder from a style resource or a themed attribute. + * + * @param builder The TextLayoutBuilder + * @param context The Context to use for resolving the attributes + * @param attrs The {@link AttributeSet} used during inflation + * @param styleAttr The themed style attribute + * @param styleRes The style resource identifier + */ + public static void updateFromStyleResource( + TextLayoutBuilder builder, + Context context, + AttributeSet attrs, + @AttrRes int styleAttr, + @StyleRes int styleRes) { + TypedArray customAttrs = context.obtainStyledAttributes( + attrs, + R.styleable.TextStyle, + styleAttr, + styleRes); + + int textAppearanceId = customAttrs.getResourceId( + R.styleable.TextStyle_android_textAppearance, + -1); + + if (textAppearanceId > 0) { + setTextAppearance(builder, context, textAppearanceId); + } + + ColorStateList textColor = customAttrs.getColorStateList( + R.styleable.TextStyle_android_textColor); + + int textSize = customAttrs.getDimensionPixelSize( + R.styleable.TextStyle_android_textSize, + DEFAULT_TEXT_SIZE_PX); + + int shadowColor = customAttrs.getInt( + R.styleable.TextStyle_android_shadowColor, + Color.TRANSPARENT); + + float dx = customAttrs.getFloat( + R.styleable.TextStyle_android_shadowDx, + 0.0f); + + float dy = customAttrs.getFloat( + R.styleable.TextStyle_android_shadowDy, + 0.0f); + + float radius = customAttrs.getFloat( + R.styleable.TextStyle_android_shadowRadius, + 0.0f); + + int textStyle = customAttrs.getInt( + R.styleable.TextStyle_android_textStyle, + -1); + + int ellipsize = customAttrs.getInt( + R.styleable.TextStyle_android_ellipsize, + 0); + + boolean singleLine = customAttrs.getBoolean( + R.styleable.TextStyle_android_singleLine, + false); + + int maxLines = customAttrs.getInt( + R.styleable.TextStyle_android_maxLines, + TextLayoutBuilder.DEFAULT_MAX_LINES); + + customAttrs.recycle(); + + builder.setTextColor(textColor); + + builder.setTextSize(textSize); + builder.setShadowLayer(radius, dx, dy, shadowColor); + + if (textStyle != -1) { + builder.setTypeface(Typeface.defaultFromStyle(textStyle)); + } else { + builder.setTypeface(null); + } + + if (ellipsize > 0 && ellipsize < 4) { + // TruncateAt doesn't have a value for NONE. + builder.setEllipsize(TextUtils.TruncateAt.values()[ellipsize - 1]); + } else { + builder.setEllipsize(null); + } + + builder.setSingleLine(singleLine); + builder.setMaxLines(maxLines); + } + + /** + * Sets a text appearance for the layout. + * + * @param builder The {@link TextLayoutBuilder} instance + * @param context The {@link Context} to use for resolving attributes + * @param resId The resource identifier of the text appearance + */ + public static void setTextAppearance( + TextLayoutBuilder builder, + Context context, + @StyleRes int resId) { + TypedArray customAttrs = context.obtainStyledAttributes( + resId, + R.styleable.TextAppearance); + + ColorStateList textColor = customAttrs.getColorStateList( + R.styleable.TextAppearance_android_textColor); + + int textSize = customAttrs.getDimensionPixelSize( + R.styleable.TextAppearance_android_textSize, + 0); + + int shadowColor = customAttrs.getInt( + R.styleable.TextAppearance_android_shadowColor, + Color.TRANSPARENT); + + if (shadowColor != Color.TRANSPARENT) { + float dx = customAttrs.getFloat( + R.styleable.TextAppearance_android_shadowDx, + 0.0f); + + float dy = customAttrs.getFloat( + R.styleable.TextAppearance_android_shadowDy, + 0.0f); + + float radius = customAttrs.getFloat( + R.styleable.TextAppearance_android_shadowRadius, + 0.0f); + + builder.setShadowLayer(radius, dx, dy, shadowColor); + } + + int textStyle = customAttrs.getInt( + R.styleable.TextAppearance_android_textStyle, + -1); + + customAttrs.recycle(); + + // Override the color only if available. + if (textColor != null) { + builder.setTextColor(textColor); + } + + if (textSize != 0) { + builder.setTextSize(textSize); + } + + // Override the style only if available. + if (textStyle != -1) { + builder.setTypeface(Typeface.defaultFromStyle(textStyle)); + } + } +} diff --git a/library/src/main/java/com/facebook/fbui/textlayoutbuilder/StaticLayoutHelper.java b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/StaticLayoutHelper.java new file mode 100644 index 0000000..5be7afd --- /dev/null +++ b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/StaticLayoutHelper.java @@ -0,0 +1,301 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.fbui.textlayoutbuilder; + +import java.lang.reflect.Field; + +import android.support.v4.text.TextDirectionHeuristicCompat; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; + +import com.facebook.fbui.textlayoutbuilder.proxy.StaticLayoutProxy; + +/** + * Helper class to get around the {@link StaticLayout} constructor limitation in ICS. + */ +/* package */ class StaticLayoutHelper { + + // Space and ellipsis to append at the end of a string to ellipsize it + private static final String SPACE_AND_ELLIPSIS = " \u2026"; + + /** + * Returns a StaticLayout using ICS specific constructor if possible. + * + * @param text The text for the layout + * @param start The start index + * @param end The end index + * @param paint The {@link TextPaint} to be used + * @param width The width of the layout + * @param alignment The {@link Layout.Alignment} + * @param spacingMult The line spacing multiplier + * @param spacingAdd The line spacing extra + * @param includePadding Whether to include font padding + * @param ellipsize The ellipsizing behavior specified by {@link TextUtils.TruncateAt} + * @param ellipsisWidth The width of the ellipsis + * @param maxLines The maximum number of lines for this layout + * @param textDirection The text direction + * @return A {@link StaticLayout} + */ + private static StaticLayout getStaticLayoutMaybeMaxLines( + CharSequence text, + int start, + int end, + TextPaint paint, + int width, + Layout.Alignment alignment, + float spacingMult, + float spacingAdd, + boolean includePadding, + TextUtils.TruncateAt ellipsize, + int ellipsisWidth, + int maxLines, + TextDirectionHeuristicCompat textDirection) { + try { + return StaticLayoutProxy.create( + text, + start, + end, + paint, + width, + alignment, + spacingMult, + spacingAdd, + includePadding, + ellipsize, + ellipsisWidth, + maxLines, + textDirection); + } catch (LinkageError e) { + // Use the publicly available constructor. + } + + return getStaticLayoutNoMaxLines( + text, + start, + end, + paint, + width, + alignment, + spacingMult, + spacingAdd, + includePadding, + ellipsize, + ellipsisWidth); + } + + /** + * Returns a StaticLayout with no maxLines restriction. + * + * @param text The text for the layout + * @param start The start index + * @param end The end index + * @param paint The {@link TextPaint} to be used + * @param width The width of the layout + * @param alignment The {@link Layout.Alignment} + * @param spacingMult The line spacing multiplier + * @param spacingAdd The line spacing extra + * @param includePadding Whether to include font padding + * @param ellipsize The ellipsizing behavior specified by {@link TextUtils.TruncateAt} + * @param ellipsisWidth The width of the ellipsis + * @return A {@link StaticLayout} with no maxLines restriction + */ + private static StaticLayout getStaticLayoutNoMaxLines( + CharSequence text, + int start, + int end, + TextPaint paint, + int width, + Layout.Alignment alignment, + float spacingMult, + float spacingAdd, + boolean includePadding, + TextUtils.TruncateAt ellipsize, + int ellipsisWidth) { + + return new StaticLayout( + text, + start, + end, + paint, + width, + alignment, + spacingMult, + spacingAdd, + includePadding, + ellipsize, + ellipsisWidth); + } + + /** + * Creates a StaticLayout will all the required properties. + * + * @param text The text for the layout + * @param start The start index + * @param end The end index + * @param paint The {@link TextPaint} to be used + * @param width The width of the layout + * @param alignment The {@link Layout.Alignment} + * @param spacingMult The line spacing multiplier + * @param spacingAdd The line spacing extra + * @param includePadding Whether to include font padding + * @param ellipsize The ellipsizing behavior specified by {@link TextUtils.TruncateAt} + * @param ellipsisWidth The width of the ellipsis + * @param maxLines The maximum number of lines for this layout + * @param textDirection The text direction + * @return A {@link StaticLayout} + */ + public static StaticLayout make( + CharSequence text, + int start, + int end, + TextPaint paint, + int width, + Layout.Alignment alignment, + float spacingMult, + float spacingAdd, + boolean includePadding, + TextUtils.TruncateAt ellipsize, + int ellipsisWidth, + int maxLines, + TextDirectionHeuristicCompat textDirection) { + + StaticLayout layout = getStaticLayoutMaybeMaxLines( + text, + start, + end, + paint, + width, + alignment, + spacingMult, + spacingAdd, + includePadding, + ellipsize, + ellipsisWidth, + maxLines, + textDirection); + + // Returned layout may not have correct line count (either because it is not supported + // pre-ICS, or because there is a bug in Android pre-Lollipop that causes the text to span + // over more lines than we asked for). We need to manually check if that happened and + // re-create Layout with a substring that will fit into required number of lines. + if (maxLines > 0) { + while (layout.getLineCount() > maxLines) { + int newEnd = layout.getLineStart(maxLines); + if (newEnd >= end) { + // to break out of a potential infinite loop + break; + } + + // newEnd is where the next line starts, not where the previous line ends + // we need to skip over the whitespace characters to get to the end of the line + while (newEnd > start) { + if (Character.isSpace(text.charAt(newEnd - 1))) { + --newEnd; + } else { + break; + } + } + + end = newEnd; + + layout = getStaticLayoutMaybeMaxLines( + text, + start, + end, + paint, + width, + alignment, + spacingMult, + spacingAdd, + includePadding, + ellipsize, + ellipsisWidth, + maxLines, + textDirection); + + if (layout.getLineCount() >= maxLines && + layout.getEllipsisCount(maxLines - 1) == 0) { + CharSequence ellipsizedText = text.subSequence(start, end) + SPACE_AND_ELLIPSIS; + layout = getStaticLayoutMaybeMaxLines( + ellipsizedText, + 0, + ellipsizedText.length(), + paint, + width, + alignment, + spacingMult, + spacingAdd, + includePadding, + ellipsize, + ellipsisWidth, + maxLines, + textDirection); + } + } + } + + while (!fixLayout(layout)) { + // try again + } + + return layout; + } + + /** + * Attempts to fix a StaticLayout with wrong layout information + * that can result in StringIndexOutOfBoundsException during layout.draw(). + * + * @param layout The {@link StaticLayout} to fix + * @return Whether the layout was fixed or not + */ + public static boolean fixLayout(StaticLayout layout) { + int lineStart = layout.getLineStart(0); + for (int i = 0, lineCount = layout.getLineCount(); i < lineCount; ++i) { + int lineEnd = layout.getLineEnd(i); + if (lineEnd < lineStart) { + // Bug, need to swap lineStart and lineEnd + try { + Field mLinesField = StaticLayout.class.getDeclaredField("mLines"); + mLinesField.setAccessible(true); + + Field mColumnsField = StaticLayout.class.getDeclaredField("mColumns"); + mColumnsField.setAccessible(true); + + int[] mLines = (int[]) mLinesField.get(layout); + int mColumns = mColumnsField.getInt(layout); + + // swap lineStart and lineEnd by swapping all the following data: + // mLines[mColumns * i.. mColumns * i+1] <-> mLines[mColumns * (i+1)..mColumns * (i+2)] + for (int j = 0; j < mColumns; ++j) { + swap(mLines, mColumns * i + j, mColumns * i + j + mColumns); + } + } catch (Exception e) { + // something is wrong, bail out + break; + } + + // start over + return false; + } + + lineStart = lineEnd; + } + + return true; + } + + private static void swap(int[] array, int i, int j) { + int tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } +} diff --git a/library/src/main/java/com/facebook/fbui/textlayoutbuilder/TextLayoutBuilder.java b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/TextLayoutBuilder.java new file mode 100644 index 0000000..8daaca3 --- /dev/null +++ b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/TextLayoutBuilder.java @@ -0,0 +1,810 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.fbui.textlayoutbuilder; + +import java.lang.annotation.Retention; + +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.support.annotation.ColorInt; +import android.support.annotation.IntDef; +import android.support.annotation.Px; +import android.support.annotation.VisibleForTesting; +import android.support.v4.text.TextDirectionHeuristicCompat; +import android.support.v4.text.TextDirectionHeuristicsCompat; +import android.support.v4.util.LruCache; +import android.text.BoringLayout; +import android.text.Layout; +import android.text.Spannable; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.style.ClickableSpan; +import android.util.Log; + +import static android.text.Layout.Alignment; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * An utility class to create text {@link Layout}s easily. + *

+ * This class uses a Builder pattern to allow re-using the same object + * to create text {@link Layout}s with similar properties. + */ +public class TextLayoutBuilder { + + /** + * Measure mode constants similar to {@link android.view.View.MeasureSpec} + * + * @see #MEASURE_MODE_UNSPECIFIED + * @see #MEASURE_MODE_EXACTLY + * @see #MEASURE_MODE_AT_MOST + */ + @Retention(SOURCE) + @IntDef({MEASURE_MODE_UNSPECIFIED, MEASURE_MODE_EXACTLY, MEASURE_MODE_AT_MOST}) + public @interface MeasureMode {} + public static final int MEASURE_MODE_UNSPECIFIED = 0; + public static final int MEASURE_MODE_EXACTLY = 1; + public static final int MEASURE_MODE_AT_MOST = 2; + + // Default maxLines. + public static final int DEFAULT_MAX_LINES = Integer.MAX_VALUE; + + // Cache for text layouts. + @VisibleForTesting + static final LruCache sCache = new LruCache<>(100); + + /** + * Params for creating the layout. + */ + @VisibleForTesting + static class Params { + TextPaint paint = new ComparableTextPaint(Paint.ANTI_ALIAS_FLAG); + int width; + @MeasureMode int measureMode; + + CharSequence text; + ColorStateList color; + + float spacingMult = 1.0f; + float spacingAdd = 0.0f; + boolean includePadding = true; + + TextUtils.TruncateAt ellipsize = null; + boolean singleLine = false; + int maxLines = DEFAULT_MAX_LINES; + Alignment alignment = Alignment.ALIGN_NORMAL; + TextDirectionHeuristicCompat textDirection = + TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR; + + boolean mForceNewPaint = false; + + /** + * Create a new paint after the builder builds for the first time. + */ + void createNewPaintIfNeeded() { + // Once after build() is called, it is not safe to set properties + // on the paint as we cache the text layouts. + // Hence we create a new paint object, + // if we ever change one of the paint's properties. + if (mForceNewPaint) { + paint = new ComparableTextPaint(paint); + mForceNewPaint = false; + } + } + + @Override + public int hashCode() { + int hashCode = 1; + hashCode = 31 * hashCode + (paint != null ? paint.hashCode() : 0); + hashCode = 31 * hashCode + width; + hashCode = 31 * hashCode + measureMode; + hashCode = 31 * hashCode + Float.floatToIntBits(spacingMult); + hashCode = 31 * hashCode + Float.floatToIntBits(spacingAdd); + hashCode = 31 * hashCode + (includePadding ? 1 : 0); + hashCode = 31 * hashCode + (ellipsize != null ? ellipsize.hashCode() : 0); + hashCode = 31 * hashCode + (singleLine ? 1 : 0); + hashCode = 31 * hashCode + maxLines; + hashCode = 31 * hashCode + (alignment != null ? alignment.hashCode() : 0); + hashCode = 31 * hashCode + (textDirection != null ? textDirection.hashCode() : 0); + hashCode = 31 * hashCode + (text != null ? text.hashCode() : 0); + + return hashCode; + } + } + + // Params for the builder. + @VisibleForTesting + final Params mParams = new Params(); + + // Locally cached layout for an instance. + private Layout mSavedLayout = null; + + // Text layout glyph warmer. + private GlyphWarmer mGlyphWarmer; + + // Cache layout or not. + private boolean mShouldCacheLayout = true; + + // Warm layout or not. + private boolean mShouldWarmText = false; + + /** + * Sets the intended width of the text layout. + * + * @param width The width of the text layout + * @return This {@link TextLayoutBuilder} instance + * @see #setWidth(int, int) + */ + public TextLayoutBuilder setWidth(@Px int width) { + return setWidth( + width, + width <= 0 ? MEASURE_MODE_UNSPECIFIED : MEASURE_MODE_EXACTLY); + } + + /** + * Sets the intended width of the text layout while respecting the measure mode. + * + * @param width The width of the text layout + * @param measureMode The mode with which to treat the given width + * @return This {@link TextLayoutBuilder} instance + * @see #setWidth(int) + */ + public TextLayoutBuilder setWidth(@Px int width, @MeasureMode int measureMode) { + if (mParams.width != width || mParams.measureMode != measureMode) { + mParams.width = width; + mParams.measureMode = measureMode; + mSavedLayout = null; + } + return this; + } + + /** + * Returns the text that would be packed in a layout by this TextLayoutBuilder. + * + * @return The text used by this TextLayoutBuilder + */ + public CharSequence getText() { + return mParams.text; + } + + /** + * Sets the text for the layout. + * + * @param text The text for the layout + * @return This {@link TextLayoutBuilder} instance + */ + public TextLayoutBuilder setText(CharSequence text) { + if (text == mParams.text || + (text != null && mParams.text != null && text.equals(mParams.text))) { + return this; + } + mParams.text = text; + mSavedLayout = null; + return this; + } + + /** + * Returns the text size for this TextLayoutBuilder. + * + * @return The text size used by this TextLayoutBuilder + */ + public float getTextSize() { + return mParams.paint.getTextSize(); + } + + /** + * Sets the text size for the layout. + * + * @param size The text size in pixels + * @return This {@link TextLayoutBuilder} instance + */ + public TextLayoutBuilder setTextSize(int size) { + if (mParams.paint.getTextSize() != size) { + mParams.createNewPaintIfNeeded(); + mParams.paint.setTextSize(size); + mSavedLayout = null; + } + return this; + } + + /** + * Returns the text color for this TextLayoutBuilder. + * + * @return The text color used by this TextLayoutBuilder + */ + @ColorInt + public int getTextColor() { + return mParams.paint.getColor(); + } + + /** + * Sets the text color for the layout. + * + * @param color The text color for the layout + * @return This {@link TextLayoutBuilder} instance + */ + public TextLayoutBuilder setTextColor(@ColorInt int color) { + mParams.createNewPaintIfNeeded(); + mParams.color = null; + mParams.paint.setColor(color); + mSavedLayout = null; + return this; + } + + /** + * Sets the text color for the layout. + * + * @param colorStateList The text color state list for the layout + * @return This {@link TextLayoutBuilder} instance + */ + public TextLayoutBuilder setTextColor(ColorStateList colorStateList) { + mParams.createNewPaintIfNeeded(); + mParams.color = colorStateList; + mParams.paint.setColor(mParams.color != null ? mParams.color.getDefaultColor() : Color.BLACK); + mSavedLayout = null; + return this; + } + + /** + * Returns the link color for this TextLayoutBuilder. + * + * @return The link color used by this TextLayoutBuilder + */ + @ColorInt + public int getLinkColor() { + return mParams.paint.linkColor; + } + + /** + * Sets the link color for the text in the layout. + * + * @param linkColor The link color + * @return This {@link TextLayoutBuilder} instance + */ + public TextLayoutBuilder setLinkColor(@ColorInt int linkColor) { + if (mParams.paint.linkColor != linkColor) { + mParams.createNewPaintIfNeeded(); + mParams.paint.linkColor = linkColor; + mSavedLayout = null; + } + return this; + } + + /** + * Returns the text spacing extra for this TextLayoutBuilder. + * + * @return The text spacing extra used by this TextLayoutBuilder + */ + public float getTextSpacingExtra() { + return mParams.spacingAdd; + } + + /** + * Sets the text extra spacing for the layout. + * + * @param spacingExtra the extra space that is added to the height of each line + * @return This {@link TextLayoutBuilder} instance + */ + public TextLayoutBuilder setTextSpacingExtra(float spacingExtra) { + if (mParams.spacingAdd != spacingExtra) { + mParams.spacingAdd = spacingExtra; + mSavedLayout = null; + } + return this; + } + + /** + * Returns the text spacing multiplier for this TextLayoutBuilder. + * + * @return The text spacing multiplier used by this TextLayoutBuilder + */ + public float getTextSpacingMultiplier() { + return mParams.spacingMult; + } + + /** + * Sets the line spacing multiplier for the layout. + * + * @param spacingMultiplier the value by which each line's height is multiplied + * @return This {@link TextLayoutBuilder} instance + */ + public TextLayoutBuilder setTextSpacingMultiplier(float spacingMultiplier) { + if (mParams.spacingMult != spacingMultiplier) { + mParams.spacingMult = spacingMultiplier; + mSavedLayout = null; + } + return this; + } + + /** + * Returns whether this TextLayoutBuilder should include font padding. + * + * @return Whether this TextLayoutBuilder should include font padding + */ + public boolean getIncludeFontPadding() { + return mParams.includePadding; + } + + /** + * Set whether the text Layout includes extra top and bottom padding to make + * room for accents that go above the normal ascent and descent. + *

+ * The default is true. + * + * @param shouldInclude Whether to include font padding or not + * @return This {@link TextLayoutBuilder} instance + */ + public TextLayoutBuilder setIncludeFontPadding(boolean shouldInclude) { + if (mParams.includePadding != shouldInclude) { + mParams.includePadding = shouldInclude; + mSavedLayout = null; + } + return this; + } + + /** + * Returns the text alignment for this TextLayoutBuilder. + * + * @return The text alignment used by this TextLayoutBuilder + */ + public Alignment getAlignment() { + return mParams.alignment; + } + + /** + * Sets text alignment for the layout. + * + * @param alignment The text alignment for the layout + * @return This {@link TextLayoutBuilder} instance + */ + public TextLayoutBuilder setAlignment(Alignment alignment) { + if (mParams.alignment != alignment) { + mParams.alignment = alignment; + mSavedLayout = null; + } + return this; + } + + /** + * Returns the text direction for this TextLayoutBuilder. + * + * @return The text direction used by this TextLayoutBuilder + */ + public TextDirectionHeuristicCompat getTextDirection() { + return mParams.textDirection; + } + + /** + * Sets the text direction heuristic for the layout. + *

+ * TextDirectionHeuristicCompat describes how to evaluate the text + * of this Layout to know whether to use RTL or LTR text direction + * + * @param textDirection The text direction heuristic for the layout + * @return This {@link TextLayoutBuilder} instance + */ + public TextLayoutBuilder setTextDirection(TextDirectionHeuristicCompat textDirection) { + if (mParams.textDirection != textDirection) { + mParams.textDirection = textDirection; + mSavedLayout = null; + } + return this; + } + + /** + * Sets the shadow layer for the layout. + * + * @param radius The radius of the blur for shadow + * @param dx The horizontal translation of the origin + * @param dy The vertical translation of the origin + * @param color The shadow color + * @return This {@link TextLayoutBuilder} instance + */ + public TextLayoutBuilder setShadowLayer(float radius, float dx, float dy, @ColorInt int color) { + mParams.createNewPaintIfNeeded(); + mParams.paint.setShadowLayer(radius, dx, dy, color); + mSavedLayout = null; + return this; + } + + /** + * Sets a text style for the layout. + * + * @param style The text style for the layout + * @return This {@link TextLayoutBuilder} instance + */ + public TextLayoutBuilder setTextStyle(int style) { + return setTypeface(Typeface.defaultFromStyle(style)); + } + + /** + * Returns the typeface for this TextLayoutBuilder. + * + * @return The typeface used by this TextLayoutBuilder + */ + public Typeface getTypeface() { + return mParams.paint.getTypeface(); + } + + /** + * Sets the typeface used by this TextLayoutBuilder. + * + * @param typeface The typeface for this TextLayoutBuilder + * @return This {@link TextLayoutBuilder} instance + */ + public TextLayoutBuilder setTypeface(Typeface typeface) { + if (mParams.paint.getTypeface() != typeface) { + mParams.createNewPaintIfNeeded(); + mParams.paint.setTypeface(typeface); + mSavedLayout = null; + } + return this; + } + + /** + * Returns the drawable state for this TextLayoutBuilder. + * + * @return The drawable state used by this TextLayoutBuilder + */ + public int[] getDrawableState() { + return mParams.paint.drawableState; + } + + /** + * Updates the text colors based on the drawable state. + * + * @param drawableState The current drawable state of the View holding this layout + * @return This {@link TextLayoutBuilder} instance + */ + public TextLayoutBuilder setDrawableState(int[] drawableState) { + mParams.createNewPaintIfNeeded(); + mParams.paint.drawableState = drawableState; + + if (mParams.color != null && mParams.color.isStateful()) { + int color = mParams.color.getColorForState(drawableState, 0); + mParams.paint.setColor(color); + mSavedLayout = null; + } + return this; + } + + /** + * Returns the text ellipsize for this TextLayoutBuilder. + * + * @return The text ellipsize used by this TextLayoutBuilder + */ + public TextUtils.TruncateAt getEllipsize() { + return mParams.ellipsize; + } + + /** + * Sets the ellipsis location for the layout. + * + * @param ellipsize The ellipsis location in the layout + * @return This {@link TextLayoutBuilder} instance + */ + public TextLayoutBuilder setEllipsize(TextUtils.TruncateAt ellipsize) { + if (mParams.ellipsize != ellipsize) { + mParams.ellipsize = ellipsize; + mSavedLayout = null; + } + return this; + } + + /** + * Returns whether the TextLayoutBuilder should show a single line. + * + * @return Whether the TextLayoutBuilder should show a single line or not + */ + public boolean getSingleLine() { + return mParams.singleLine; + } + + /** + * Sets whether the text should be in a single line or not. + * + * @param singleLine Whether the text should be in a single line or not + * @return This {@link TextLayoutBuilder} instance + * @see #setMaxLines(int) + */ + public TextLayoutBuilder setSingleLine(boolean singleLine) { + if (mParams.singleLine != singleLine) { + mParams.singleLine = singleLine; + mSavedLayout = null; + } + return this; + } + + /** + * Returns the number of max lines used by this TextLayoutBuilder. + * + * @return The number of max lines for this TextLayoutBuilder + */ + public int getMaxLines() { + return mParams.maxLines; + } + + /** + * Sets a maximum number of lines to be shown by the Layout. + *

+ * Note: Gingerbread always default to two lines max when ellipsized. This cannot be changed. + * Use a TextView if you want more control over the number of lines. + * + * @param maxLines The number of maxLines to show in this Layout + * @return This {@link TextLayoutBuilder} instance + * @see #setSingleLine(boolean) + */ + public TextLayoutBuilder setMaxLines(int maxLines) { + if (mParams.maxLines != maxLines) { + mParams.maxLines = maxLines; + mSavedLayout = null; + } + return this; + } + + /** + * Returns whether the TextLayoutBuilder should cache the layout. + * + * @return Whether the TextLayoutBuilder should cache the layout + */ + public boolean getShouldCacheLayout() { + return mShouldCacheLayout; + } + + /** + * Sets whether the text layout should be cached or not. + *

+ * Note: If the Layout contains {@link ClickableSpan}s, the layout will not be cached. + * + * @param shouldCacheLayout True to cache the text layout, false otherwise + * @return This {@link TextLayoutBuilder} instance + */ + public TextLayoutBuilder setShouldCacheLayout(boolean shouldCacheLayout) { + mShouldCacheLayout = shouldCacheLayout; + return this; + } + + /** + * Returns whether the TextLayoutBuilder should warm the layout. + * + * @return Whether the TextLayoutBuilder should warm the layout + */ + public boolean getShouldWarmText() { + return mShouldWarmText; + } + + /** + * Sets whether the text should be warmed or not. + *

+ * Note: Setting this true is highly effective for large blurbs of text. + * This method has to be called before the draw pass. + * + * @param shouldWarmText True to warm the text layout, false otherwise + * @return This {@link TextLayoutBuilder} instance + * @see #setGlyphWarmer(GlyphWarmer) + */ + public TextLayoutBuilder setShouldWarmText(boolean shouldWarmText) { + mShouldWarmText = shouldWarmText; + return this; + } + + /** + * Returns the GlyphWarmer used by the TextLayoutBuilder. + * + * @return The GlyphWarmer for this TextLayoutBuilder + */ + public GlyphWarmer getGlyphWarmer() { + return mGlyphWarmer; + } + + /** + * Sets the glyph warmer to use. + * + * @param glyphWarmer GlyphWarmer to use to warm the text layout + * @return This {@link TextLayoutBuilder} instance + * @see #setShouldWarmText(boolean) + */ + public TextLayoutBuilder setGlyphWarmer(GlyphWarmer glyphWarmer) { + mGlyphWarmer = glyphWarmer; + return this; + } + + /** + * Builds and returns a {@link Layout}. + * + * @return A {@link Layout} based on the parameters set + */ + public Layout build() { + // Return the cached layout if no property changed. + if (mShouldCacheLayout && mSavedLayout != null) { + return mSavedLayout; + } + + if (TextUtils.isEmpty(mParams.text)) { + return null; + } + + boolean hasClickableSpans = false; + int hashCode = -1; + + if (mShouldCacheLayout && mParams.text instanceof Spannable) { + ClickableSpan[] spans = ((Spannable) mParams.text).getSpans( + 0, + mParams.text.length() - 1, + ClickableSpan.class); + hasClickableSpans = spans.length > 0; + } + + // If the text has ClickableSpans, it will be bound to different + // click listeners each time. It is unsafe to cache these text Layouts. + // Hence they will not be in cache. + if (mShouldCacheLayout && !hasClickableSpans) { + hashCode = mParams.hashCode(); + Layout cachedLayout = sCache.get(hashCode); + if (cachedLayout != null) { + return cachedLayout; + } + } + + BoringLayout.Metrics metrics = null; + + int numLines = mParams.singleLine ? 1 : mParams.maxLines; + + // Try creating a boring layout only if singleLine is requested. + if (numLines == 1) { + metrics = BoringLayout.isBoring(mParams.text, mParams.paint); + } + + // getDesiredWidth here is used to ensure we layout text at the same size which it is measured. + // If we used a large static value it would break RTL due to drawing text at the very end of the + // large value. + int width; + switch (mParams.measureMode) { + case MEASURE_MODE_UNSPECIFIED: + width = (int) Math.ceil(Layout.getDesiredWidth(mParams.text, mParams.paint)); + break; + case MEASURE_MODE_EXACTLY: + width = mParams.width; + break; + case MEASURE_MODE_AT_MOST: + width = + Math.min( + (int) Math.ceil(Layout.getDesiredWidth(mParams.text, mParams.paint)), + mParams.width); + break; + default: + throw new IllegalStateException("Unexpected measure mode " + mParams.measureMode); + } + + Layout layout; + if (metrics != null) { + layout = BoringLayout.make( + mParams.text, + mParams.paint, + width, + mParams.alignment, + mParams.spacingMult, + mParams.spacingAdd, + metrics, + mParams.includePadding, + mParams.ellipsize, + width); + } else { + while (true) { + try { + layout = StaticLayoutHelper.make( + mParams.text, + 0, + mParams.text.length(), + mParams.paint, + width, + mParams.alignment, + mParams.spacingMult, + mParams.spacingAdd, + mParams.includePadding, + mParams.ellipsize, + width, + numLines, + mParams.textDirection); + } catch (IndexOutOfBoundsException e) { + // Workaround for https://code.google.com/p/android/issues/detail?id=35412 + if (!(mParams.text instanceof String)) { + // remove all Spannables and re-try + Log.e("TextLayoutBuilder", "Hit bug #35412, retrying with Spannables removed", e); + mParams.text = mParams.text.toString(); + continue; + } else { + // If it still happens with all Spannables removed we'll bubble the exception up + throw e; + } + } + + break; + } + } + + // Do not cache if the text has ClickableSpans. + if (mShouldCacheLayout && !hasClickableSpans) { + mSavedLayout = layout; + sCache.put(hashCode, layout); + } + + // Force a new paint. + mParams.mForceNewPaint = true; + + if (mShouldWarmText && mGlyphWarmer != null) { + // Draw the text in a background thread to warm the cache. + mGlyphWarmer.warmLayout(layout); + } + + return layout; + } + + /** + * A comparable version of {@link TextPaint}. + */ + private static class ComparableTextPaint extends TextPaint { + + private float mShadowDx; + private float mShadowDy; + private float mShadowRadius; + private int mShadowColor; + + public ComparableTextPaint() { + super(); + } + + public ComparableTextPaint(int flags) { + super(flags); + } + + public ComparableTextPaint(Paint p) { + super(p); + } + + @Override + public void setShadowLayer(float radius, float dx, float dy, int color) { + mShadowRadius = radius; + mShadowDx = dx; + mShadowDy = dy; + mShadowColor = color; + + super.setShadowLayer(radius, dx, dy, color); + } + + @Override + public int hashCode() { + Typeface tf = getTypeface(); + + int hashCode = 1; + hashCode = 31 * hashCode + getColor(); + hashCode = 31 * hashCode + Float.floatToIntBits(getTextSize()); + hashCode = 31 * hashCode + (tf != null ? tf.hashCode() : 0); + hashCode = 31 * hashCode + Float.floatToIntBits(mShadowDx); + hashCode = 31 * hashCode + Float.floatToIntBits(mShadowDy); + hashCode = 31 * hashCode + Float.floatToIntBits(mShadowRadius); + hashCode = 31 * hashCode + mShadowColor; + hashCode = 31 * hashCode + linkColor; + + // Array. + if (drawableState == null) { + hashCode = 31 * hashCode + 0; + } else { + for (int i = 0; i < drawableState.length; i++) { + hashCode = 31 * hashCode + drawableState[i]; + } + } + + return hashCode; + } + } +} diff --git a/library/src/main/java/com/facebook/fbui/textlayoutbuilder/glyphwarmer/GlyphWarmerImpl.java b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/glyphwarmer/GlyphWarmerImpl.java new file mode 100644 index 0000000..2509746 --- /dev/null +++ b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/glyphwarmer/GlyphWarmerImpl.java @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.fbui.textlayoutbuilder.glyphwarmer; + +import android.annotation.SuppressLint; +import android.graphics.Canvas; +import android.graphics.Picture; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.support.annotation.VisibleForTesting; +import android.text.Layout; + +import com.facebook.fbui.textlayoutbuilder.GlyphWarmer; +import com.facebook.fbui.textlayoutbuilder.util.LayoutMeasureUtil; + +/** + * Default {@link GlyphWarmer} that runs a {@link HandlerThread} + * to draw a text {@link Layout} on a {@link Picture}. + * This helps in warming up the glyph cache in Android 4.0+. + */ +public class GlyphWarmerImpl implements GlyphWarmer { + + // Handler for the HandlerThread. + private static WarmHandler sWarmHandler; + + @Override + public void warmLayout(Layout layout) { + WarmHandler handler = getWarmHandler(); + handler.sendMessage(handler.obtainMessage(WarmHandler.NO_OP, layout)); + } + + @VisibleForTesting + Looper getWarmHandlerLooper() { + return getWarmHandler().getLooper(); + } + + @SuppressLint({ + "BadMethodUse-android.os.HandlerThread._Constructor", + "BadMethodUse-java.lang.Thread.start" + }) + private WarmHandler getWarmHandler() { + if (sWarmHandler == null) { + // A text warmer thread to render the the layout in the background. + // This helps warm the layout cache in Android 4.0+ devices, + // making large blurbs of text to draw in 0ms. + HandlerThread warmerThread = new HandlerThread("GlyphWarmer"); + warmerThread.start(); + + // Note: warmerThread.getLooper() can return null. + sWarmHandler = new WarmHandler(warmerThread.getLooper()); + } + + return sWarmHandler; + } + + /** + * A handler to send messages to the GlyphWarmerImpl thread. + */ + private static class WarmHandler extends Handler { + + private static final int NO_OP = 1; + + private final Picture mPicture = new Picture(); + + public WarmHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + Layout layout = (Layout) msg.obj; + try { + Canvas canvas = mPicture.beginRecording( + LayoutMeasureUtil.getWidth(layout), + LayoutMeasureUtil.getHeight(layout)); + layout.draw(canvas); + mPicture.endRecording(); + } catch (Exception e) { + // Do nothing. + } + } + } +} diff --git a/library/src/main/java/com/facebook/fbui/textlayoutbuilder/glyphwarmer/package-info.java b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/glyphwarmer/package-info.java new file mode 100644 index 0000000..34175cc --- /dev/null +++ b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/glyphwarmer/package-info.java @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/** + * Provides a default implementation of {@link com.facebook.fbui.textlayoutbuilder.GlyphWarmer}. + * @see com.facebook.fbui.textlayoutbuilder.GlyphWarmer + */ + +package com.facebook.fbui.textlayoutbuilder.glyphwarmer; diff --git a/library/src/main/java/com/facebook/fbui/textlayoutbuilder/package-info.java b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/package-info.java new file mode 100644 index 0000000..4fc06d8 --- /dev/null +++ b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/package-info.java @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/** + * Provides the classes to build text {@link android.text.Layout}s easily. + *

+ * {@link com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder} is an utility class that creates + * text {@link android.text.Layout}s easily. This package contains classes that interact with + * {@link com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder}. + */ + +package com.facebook.fbui.textlayoutbuilder; diff --git a/library/src/main/java/com/facebook/fbui/textlayoutbuilder/util/LayoutMeasureUtil.java b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/util/LayoutMeasureUtil.java new file mode 100644 index 0000000..2b0508c --- /dev/null +++ b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/util/LayoutMeasureUtil.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.fbui.textlayoutbuilder.util; + +import android.os.Build; +import android.text.Layout; +import android.text.StaticLayout; + +/** + * Utility Class for measuring text {@link Layout}s. + */ +public class LayoutMeasureUtil { + + /** + * Returns the width of the layout. + * + * @param layout The layout + * @return The width of the layout + */ + public static int getWidth(Layout layout) { + if (layout == null) { + return 0; + } + + // Supplying VERY_WIDE will make layout.getWidth() return a very large value. + int count = layout.getLineCount(); + int maxWidth = 0; + + for (int i = 0; i < count; i++) { + maxWidth = Math.max(maxWidth, (int) layout.getLineRight(i)); + } + + return maxWidth; + } + + /** + * Prior to version 20, If the Layout specifies extra space between lines (either by spacingmult + * or spacingadd) the StaticLayout would erroneously add this space after the last line as well. + * This bug was fixed in version 20. This method calculates the extra space and reduces the height + * by that amount. + * + * @param layout The layout + * @return The height of the layout + */ + public static int getHeight(Layout layout) { + if (layout == null) { + return 0; + } + + int extra = 0; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT_WATCH && + layout instanceof StaticLayout) { + int above = layout.getLineAscent(layout.getLineCount() - 1); + int below = layout.getLineDescent(layout.getLineCount() - 1); + float originalSize = (below - above - layout.getSpacingAdd()) / layout.getSpacingMultiplier(); + float ex = below - above - originalSize; + if (ex >= 0) { + extra = (int) (ex + 0.5); + } else { + extra = -(int) (-ex + 0.5); + } + } + return layout.getHeight() - extra; + } +} diff --git a/library/src/main/java/com/facebook/fbui/textlayoutbuilder/util/package-info.java b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/util/package-info.java new file mode 100644 index 0000000..1805856 --- /dev/null +++ b/library/src/main/java/com/facebook/fbui/textlayoutbuilder/util/package-info.java @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/** + * Provides an utility class to measure text {@link android.text.Layout}s. + */ + +package com.facebook.fbui.textlayoutbuilder.util; diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml new file mode 100644 index 0000000..8af0b1b --- /dev/null +++ b/library/src/main/res/values/attrs.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/src/test/java/com/facebook/fbui/textlayoutbuilder/TextLayoutBuilderTest.java b/library/src/test/java/com/facebook/fbui/textlayoutbuilder/TextLayoutBuilderTest.java new file mode 100644 index 0000000..bd4a0c4 --- /dev/null +++ b/library/src/test/java/com/facebook/fbui/textlayoutbuilder/TextLayoutBuilderTest.java @@ -0,0 +1,329 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.fbui.textlayoutbuilder; + +import android.content.res.ColorStateList; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.support.v4.text.TextDirectionHeuristicsCompat; +import android.text.Layout; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.ClickableSpan; +import android.text.style.StyleSpan; +import android.view.View; + +import com.facebook.fbui.textlayoutbuilder.shadows.ShadowPicture; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +/** + * Tests {@link TextLayoutBuilder} + */ +@Config(manifest = Config.NONE, shadows = {ShadowPicture.class}) +@RunWith(RobolectricTestRunner.class) +public class TextLayoutBuilderTest { + + private static final String TEST = "TEST"; + private static final String LONG_TEXT = + "Lorem ipsum dolor sit amet test \n" + + "Lorem ipsum dolor sit amet test \n" + + "Lorem ipsum dolor sit amet test \n" + + "Lorem ipsum dolor sit amet test \n"; + + private TextLayoutBuilder mBuilder; + private Layout mLayout; + + @Before + public void setup() { + mBuilder = new TextLayoutBuilder(); + mBuilder.setText(TEST); + + // Clear the cache. + mBuilder.sCache.evictAll(); + } + + // Test setters. + @Test + public void testSetText() { + mLayout = mBuilder.setText("Android").build(); + assertEquals(mBuilder.getText(), "Android"); + assertEquals(mLayout.getText(), "Android"); + } + + @Test + public void testSetTextNull() { + mLayout = mBuilder.setText(null).build(); + assertEquals(mBuilder.getText(), null); + assertEquals(mLayout, null); + } + + @Test + public void testSetTextSize() { + mLayout = mBuilder.setTextSize(10).build(); + assertEquals(mBuilder.getTextSize(), 10.0f, 0.0f); + assertEquals(mLayout.getPaint().getTextSize(), 10.0f, 0.0f); + } + + @Test + public void testSetTextColor() { + mLayout = mBuilder.setTextColor(0xFFFF0000).build(); + assertEquals(mBuilder.getTextColor(), 0xFFFF0000); + assertEquals(mLayout.getPaint().getColor(), 0xFFFF0000); + } + + @Test + public void testSetTextColorStateList() { + mLayout = mBuilder.setTextColor(ColorStateList.valueOf(0xFFFF0000)).build(); + assertEquals(mBuilder.getTextColor(), 0xFFFF0000); + assertEquals(mLayout.getPaint().getColor(), 0xFFFF0000); + } + + @Test + public void testSetLinkColor() { + mLayout = mBuilder.setLinkColor(0xFFFF0000).build(); + assertEquals(mBuilder.getLinkColor(), 0xFFFF0000); + assertEquals(mLayout.getPaint().linkColor, 0xFFFF0000); + } + + @Test + public void testSetTextSpacingExtra() { + mLayout = mBuilder.setTextSpacingExtra(10).build(); + assertEquals(mBuilder.getTextSpacingExtra(), 10.0f, 0.0f); + assertEquals(mLayout.getSpacingAdd(), 10.0f, 0.0f); + } + + @Test + public void testSetTextSpacingMultiplier() { + mLayout = mBuilder.setTextSpacingMultiplier(1.5f).build(); + assertEquals(mBuilder.getTextSpacingMultiplier(), 1.5f, 0.0f); + assertEquals(mLayout.getSpacingMultiplier(), 1.5f, 0.0f); + } + + @Test + public void testSetIncludeFontPadding() { + mLayout = mBuilder.setIncludeFontPadding(false).build(); + assertEquals(mBuilder.getIncludeFontPadding(), false); + } + + @Test + public void testSetAlignment() { + mLayout = mBuilder.setAlignment(Layout.Alignment.ALIGN_CENTER).build(); + assertEquals(mBuilder.getAlignment(), Layout.Alignment.ALIGN_CENTER); + assertEquals(mLayout.getAlignment(), Layout.Alignment.ALIGN_CENTER); + } + + @Test + public void testSetTextDirection() { + mLayout = mBuilder.setTextDirection(TextDirectionHeuristicsCompat.LOCALE).build(); + assertEquals(mBuilder.getTextDirection(), TextDirectionHeuristicsCompat.LOCALE); + } + + @Test + public void testSetTypeface() { + mLayout = mBuilder.setTypeface(Typeface.MONOSPACE).build(); + assertEquals(mBuilder.getTypeface(), Typeface.MONOSPACE); + } + + @Test + public void testSetEllipsize() { + mLayout = mBuilder.setEllipsize(TextUtils.TruncateAt.MARQUEE).build(); + assertEquals(mBuilder.getEllipsize(), TextUtils.TruncateAt.MARQUEE); + } + + @Test + public void testSetSingleLine() { + mLayout = mBuilder.setSingleLine(true).build(); + assertEquals(mBuilder.getSingleLine(), true); + } + + @Test + public void testSetMaxLines() { + mLayout = mBuilder.setMaxLines(10).build(); + assertEquals(mBuilder.getMaxLines(), 10.0f, 0.0f); + } + + @Test + public void testSetShouldCacheLayout() { + mLayout = mBuilder.setShouldCacheLayout(false).build(); + assertEquals(mBuilder.getShouldCacheLayout(), false); + } + + @Test + public void testSetShouldWarmText() { + mLayout = mBuilder.setShouldWarmText(true).build(); + assertEquals(mBuilder.getShouldWarmText(), true); + } + + @Test + public void testSetGlyphWarmer() { + FakeGlyphWarmer glyphWarmer = new FakeGlyphWarmer(); + mLayout = mBuilder.setGlyphWarmer(glyphWarmer).build(); + assertEquals(mBuilder.getGlyphWarmer(), glyphWarmer); + } + + // Test functionality. + @Test + public void testSingleLine() { + mLayout = mBuilder + .setText(LONG_TEXT) + .setSingleLine(true) + .setWidth(1000) + .build(); + assertEquals(mLayout.getLineCount(), 1); + } + + @Test + public void testMaxLines() { + mLayout = mBuilder + .setText(LONG_TEXT) + .setMaxLines(2) + .setWidth(1000) + .build(); + assertEquals(mLayout.getLineCount(), 2); + } + + @Test + public void testDrawableState() { + int[] drawableState = {0, 1}; + mLayout = mBuilder.setDrawableState(drawableState).build(); + assertArrayEquals(mBuilder.getDrawableState(), drawableState); + } + + @Test + public void testNewPaint() { + Paint oldPaint = mBuilder.mParams.paint; + + // Build the current builder. + mBuilder.build(); + + // Change paint properties. + mBuilder.setShadowLayer(10.0f, 1.0f, 1.0f, 0); + Paint newPaint = mBuilder.mParams.paint; + assertNotEquals(oldPaint, newPaint); + } + + @Test + public void testWarmText() { + FakeGlyphWarmer warmer = new FakeGlyphWarmer(); + mLayout = mBuilder + .setShouldWarmText(true) + .setGlyphWarmer(warmer) + .build(); + assertEquals(warmer.getLayout(), mLayout); + } + + @Test + public void testDoNotWarmText() { + FakeGlyphWarmer warmer = new FakeGlyphWarmer(); + mLayout = mBuilder + .setShouldWarmText(false) + .setGlyphWarmer(warmer) + .build(); + assertEquals(warmer.getLayout(), null); + } + + @Test + public void testCaching() { + mLayout = mBuilder.setShouldCacheLayout(true).build(); + Layout newLayout = mBuilder.build(); + assertEquals(mLayout, newLayout); + assertEquals(mBuilder.sCache.size(), 1); + assertEquals(mBuilder.sCache.get(mBuilder.mParams.hashCode()), mLayout); + } + + @Test + public void testNoCaching() { + mLayout = mBuilder.setShouldCacheLayout(false).build(); + Layout newLayout = mBuilder.build(); + assertNotEquals(mLayout, newLayout); + assertEquals(mBuilder.sCache.size(), 0); + assertEquals(mBuilder.sCache.get(mBuilder.mParams.hashCode()), null); + } + + @Test + public void testTwoBuildersWithSameParamsAndCaching() { + mLayout = mBuilder.setShouldCacheLayout(true).build(); + + TextLayoutBuilder newBuilder = new TextLayoutBuilder(); + Layout newLayout = newBuilder.setText(TEST).setShouldCacheLayout(true).build(); + assertEquals(mLayout, newLayout); + } + + @Test + public void testTwoBuildersWithSameParamsAndNoCaching() { + mLayout = mBuilder.setShouldCacheLayout(false).build(); + + TextLayoutBuilder newBuilder = new TextLayoutBuilder(); + Layout newLayout = newBuilder.setText(TEST).setShouldCacheLayout(false).build(); + assertNotEquals(mLayout, newLayout); + } + + @Test + public void testSpannableString() { + SpannableStringBuilder spannable = new SpannableStringBuilder("This is a bold text"); + spannable.setSpan(new StyleSpan(Typeface.BOLD), 10, 13, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + mLayout = mBuilder.setText(spannable).build(); + assertEquals(mLayout.getText(), spannable); + } + + @Test + public void testCachingSpannableString() { + SpannableStringBuilder spannable = new SpannableStringBuilder("This is a bold text"); + spannable.setSpan(new StyleSpan(Typeface.BOLD), 10, 13, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + mLayout = mBuilder + .setText(spannable) + .setShouldCacheLayout(true) + .build(); + assertEquals(mBuilder.sCache.size(), 1); + assertEquals(mBuilder.sCache.get(mBuilder.mParams.hashCode()), mLayout); + } + + @Test + public void testNoCachingSpannableString() { + ClickableSpan clickableSpan = new ClickableSpan() { + @Override + public void onClick(View widget) { + // Do nothing. + } + }; + + SpannableStringBuilder spannable = new SpannableStringBuilder("This is a bold text"); + spannable.setSpan(clickableSpan, 10, 13, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + mLayout = mBuilder + .setText(spannable) + .setShouldCacheLayout(true) + .build(); + assertEquals(mBuilder.sCache.size(), 0); + assertEquals(mBuilder.sCache.get(mBuilder.mParams.hashCode()), null); + } + + private static class FakeGlyphWarmer implements GlyphWarmer { + private Layout mLayout = null; + + @Override + public void warmLayout(Layout layout) { + mLayout = layout; + } + + Layout getLayout() { + return mLayout; + } + } +} diff --git a/library/src/test/java/com/facebook/fbui/textlayoutbuilder/TextMeasureModeTest.java b/library/src/test/java/com/facebook/fbui/textlayoutbuilder/TextMeasureModeTest.java new file mode 100644 index 0000000..826d63c --- /dev/null +++ b/library/src/test/java/com/facebook/fbui/textlayoutbuilder/TextMeasureModeTest.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.fbui.textlayoutbuilder; + +import android.graphics.Typeface; +import android.text.Layout; + +import com.facebook.fbui.textlayoutbuilder.shadows.ShadowLayout; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder.MEASURE_MODE_AT_MOST; +import static com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder.MEASURE_MODE_EXACTLY; +import static com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder.MEASURE_MODE_UNSPECIFIED; +import static org.junit.Assert.assertEquals; + +@Config(manifest = Config.NONE, shadows = {ShadowLayout.class}) +@RunWith(RobolectricTestRunner.class) +public class TextMeasureModeTest { + + @Test + public void testMeasureModeUnspecified() { + final Layout layout = new TextLayoutBuilder() + .setText(ShadowLayout.LONG_TEXT) + .setWidth(20, MEASURE_MODE_UNSPECIFIED) + .setTypeface(Typeface.DEFAULT) + .setTextSize(10) + .build(); + + assertEquals(ShadowLayout.LONG_TEXT_LENGTH, layout.getWidth()); + } + + @Test + public void testMeasureModeExactly() { + final Layout layout = new TextLayoutBuilder() + .setText(ShadowLayout.LONG_TEXT) + .setWidth(20, MEASURE_MODE_EXACTLY) + .setTypeface(Typeface.DEFAULT) + .setTextSize(10) + .build(); + + assertEquals(20, layout.getWidth()); + } + + @Test + public void testMeasureModeAtMostLongText() { + final Layout layout = new TextLayoutBuilder() + .setText(ShadowLayout.LONG_TEXT) + .setWidth(20, MEASURE_MODE_AT_MOST) + .setTypeface(Typeface.DEFAULT) + .setTextSize(10) + .build(); + + assertEquals(20, layout.getWidth()); + } + + @Test + public void testMeasureModeAtMostShortText() { + final Layout layout = new TextLayoutBuilder() + .setText(ShadowLayout.SHORT_TEXT) + .setWidth(20, MEASURE_MODE_AT_MOST) + .setTypeface(Typeface.DEFAULT) + .setTextSize(10) + .build(); + + assertEquals(ShadowLayout.SHORT_TEXT_LENGTH, layout.getWidth()); + } + + @Test + public void testLegacyBehaviour() { + final Layout layout = new TextLayoutBuilder() + .setText(ShadowLayout.LONG_TEXT) + .setWidth(-1) + .setTypeface(Typeface.DEFAULT) + .setTextSize(10) + .build(); + + assertEquals(ShadowLayout.LONG_TEXT_LENGTH, layout.getWidth()); + } +} diff --git a/library/src/test/java/com/facebook/fbui/textlayoutbuilder/glyphwarmer/GlyphWarmerImplTest.java b/library/src/test/java/com/facebook/fbui/textlayoutbuilder/glyphwarmer/GlyphWarmerImplTest.java new file mode 100644 index 0000000..31ebbab --- /dev/null +++ b/library/src/test/java/com/facebook/fbui/textlayoutbuilder/glyphwarmer/GlyphWarmerImplTest.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.fbui.textlayoutbuilder.glyphwarmer; + +import android.graphics.Canvas; +import android.text.Layout; + +import com.facebook.fbui.textlayoutbuilder.shadows.ShadowPicture; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.internal.ShadowExtractor; +import org.robolectric.shadows.ShadowLooper; + +import static org.mockito.Mockito.verify; +import static org.mockito.Matchers.any; + +/** + * Tests {@link GlyphWarmerImpl}. + */ +@Config(manifest = Config.NONE, shadows = {ShadowPicture.class}) +@RunWith(RobolectricTestRunner.class) +public class GlyphWarmerImplTest { + + @Mock Layout mLayout; + + private ShadowLooper mShadowLooper; + private GlyphWarmerImpl mGlyphWarmerImpl; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + mGlyphWarmerImpl = new GlyphWarmerImpl(); + mShadowLooper = (ShadowLooper) ShadowExtractor.extract(mGlyphWarmerImpl.getWarmHandlerLooper()); + } + + @Test + public void testWarmGlyph() { + mGlyphWarmerImpl.warmLayout(mLayout); + mShadowLooper.runOneTask(); + verify(mLayout).draw(any(Canvas.class)); + } + + @Test + public void testWarmGlyphForNullLayout() { + mGlyphWarmerImpl.warmLayout(null); + mShadowLooper.runOneTask(); + } +} diff --git a/library/src/test/java/com/facebook/fbui/textlayoutbuilder/shadows/ShadowLayout.java b/library/src/test/java/com/facebook/fbui/textlayoutbuilder/shadows/ShadowLayout.java new file mode 100644 index 0000000..5e513a3 --- /dev/null +++ b/library/src/test/java/com/facebook/fbui/textlayoutbuilder/shadows/ShadowLayout.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.fbui.textlayoutbuilder.shadows; + +import android.text.Layout; +import android.text.TextPaint; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(Layout.class) +public class ShadowLayout { + + public static final String LONG_TEXT = "ZzZzZzZzZzZzZzZzZzZzZz"; + public static final String SHORT_TEXT = "Z"; + public static final int LONG_TEXT_LENGTH = 100; + public static final int SHORT_TEXT_LENGTH = 5; + + @Implementation + public static float getDesiredWidth(CharSequence source, TextPaint paint) { + if (LONG_TEXT.equals(source)) { + return LONG_TEXT_LENGTH; + } else if (SHORT_TEXT.equals(source)) { + return SHORT_TEXT_LENGTH; + } + return 0; + } +} diff --git a/library/src/test/java/com/facebook/fbui/textlayoutbuilder/shadows/ShadowPicture.java b/library/src/test/java/com/facebook/fbui/textlayoutbuilder/shadows/ShadowPicture.java new file mode 100644 index 0000000..ac91426 --- /dev/null +++ b/library/src/test/java/com/facebook/fbui/textlayoutbuilder/shadows/ShadowPicture.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.fbui.textlayoutbuilder.shadows; + +import android.graphics.Canvas; +import android.graphics.Picture; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(Picture.class) +public class ShadowPicture { + + @Implementation + public void __constructor__(int nativePicture, boolean fromStream) { + // Do nothing. + } + + @Implementation + public void __constructor__(int nativePicture) { + // Do nothing. + } + + @Implementation + public void __constructor__() { + // Do nothing. + } + + @Implementation + public Canvas beginRecording(int width, int height) { + return new Canvas(); + } +} diff --git a/library/src/test/java/com/facebook/fbui/textlayoutbuilder/util/LayoutMeasureUtilTest.java b/library/src/test/java/com/facebook/fbui/textlayoutbuilder/util/LayoutMeasureUtilTest.java new file mode 100644 index 0000000..b5627a2 --- /dev/null +++ b/library/src/test/java/com/facebook/fbui/textlayoutbuilder/util/LayoutMeasureUtilTest.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.fbui.textlayoutbuilder.util; + +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.junit.Assert.assertEquals; + +/** + * Tests {@link LayoutMeasureUtil} + */ +@Config(manifest = Config.NONE) +@RunWith(RobolectricTestRunner.class) +public class LayoutMeasureUtilTest { + + private static final String ONE_LINE_TEXT = "test"; + private static final String TWO_LINE_TEXT = "test\ntest"; + + private Layout mLayout; + + @Test + public void testOneLineWithAdd() { + mLayout = StaticLayoutHelper.makeStaticLayout(ONE_LINE_TEXT, 1.0f, 5.0f); + assertEquals(LayoutMeasureUtil.getHeight(mLayout), 10); + } + + @Test + public void testTwoLinesWithAdd() { + mLayout = StaticLayoutHelper.makeStaticLayout(TWO_LINE_TEXT, 1.0f, 5.0f); + assertEquals(LayoutMeasureUtil.getHeight(mLayout), 25); + } + + @Test + public void testOneLineWithMulti() { + mLayout = StaticLayoutHelper.makeStaticLayout(ONE_LINE_TEXT, 1.5f, 0.0f); + assertEquals(LayoutMeasureUtil.getHeight(mLayout), 10); + } + + @Test + public void testTwoLinesWithMulti() { + mLayout = StaticLayoutHelper.makeStaticLayout(TWO_LINE_TEXT, 1.5f, 0.0f); + assertEquals(LayoutMeasureUtil.getHeight(mLayout), 25); + } + + @Test + public void testOneLineWithAddAndMulti() { + mLayout = StaticLayoutHelper.makeStaticLayout(ONE_LINE_TEXT, 1.5f, 2.0f); + assertEquals(LayoutMeasureUtil.getHeight(mLayout), 10); + } + + @Test + public void testTwoLinesWithAddAndMulti() { + mLayout = StaticLayoutHelper.makeStaticLayout(TWO_LINE_TEXT, 1.5f, 2.0f); + assertEquals(LayoutMeasureUtil.getHeight(mLayout), 27); + } + + @Test + public void testEmptyTextWithAddAndMulti() { + mLayout = StaticLayoutHelper.makeStaticLayout("", 1.5f, 2.0f); + assertEquals(LayoutMeasureUtil.getHeight(mLayout), 10); + } + + // TextPaint with a line height of 10. + private static class DummyTextPaint extends TextPaint { + @Override + public int getFontMetricsInt(FontMetricsInt fmi) { + if (fmi != null) { + fmi.ascent = 0; + fmi.top = 0; + fmi.descent = 10; + fmi.bottom = 10; + } + return 0; + } + } + + private static class StaticLayoutHelper { + public static Layout makeStaticLayout( + CharSequence text, + float spacingMult, + float spacingAdd) { + return new StaticLayout( + text, + new DummyTextPaint(), + 1000, + Layout.Alignment.ALIGN_NORMAL, + spacingMult, + spacingAdd, + true); + } + } +} diff --git a/proguard-android.txt b/proguard-android.txt new file mode 100644 index 0000000..c10c9fd --- /dev/null +++ b/proguard-android.txt @@ -0,0 +1,7 @@ +# This is a configuration file for ProGuard. +# For more details about adding this proguard file to your app: +# https://developer.android.com/studio/projects/android-library.html + +# This library uses a non-public Android constructor within StaticLayout. +# See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details. +-dontwarn android.text.StaticLayout diff --git a/release-sonatype.gradle b/release-sonatype.gradle new file mode 100644 index 0000000..5dc6b35 --- /dev/null +++ b/release-sonatype.gradle @@ -0,0 +1,92 @@ +// Definitions of release tasks. +// In order to upload jars to maven repository run: +// $ ./gradlew [-P askForPasswords=true] uploadArchives +// "-P askForPasswords" is optional. Including it in the command will force +// gradle to ask you for passwords rather than read them from gradle.properties. +// +// In order to upload jars to local maven repository run: +//$ ./gradlew -P repositoryUrl=file://localhost/ uploadArchives + + +apply plugin: 'maven' +apply plugin: 'signing' + +def isReleaseBuild() { + return VERSION_NAME.contains("SNAPSHOT") == false +} + +def getRepositoryUrl() { + return hasProperty('repositoryUrl') ? property('repositoryUrl') : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" +} + +def getRepositoryUsername() { + return hasProperty('repositoryUsername') ? property('repositoryUsername') : "" +} + +def getRepositoryPassword() { + return hasProperty('repositoryPassword') ? property('repositoryPassword') : "" +} + +def getPassword(String passwordName) { + return new String(System.console().readPassword("\nProvide $passwordName:")) +} + +if (hasProperty('askForPasswords')) { + ext.'repositoryPassword' = getPassword('repository password'); + ext.'signing.password' = getPassword('signing password'); +} + +afterEvaluate { project -> + version = VERSION_NAME + group = GROUP + + signing { + required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } + sign configurations.archives + } + + uploadArchives { + configuration = configurations.archives + repositories.mavenDeployer { + beforeDeployment { + MavenDeployment deployment -> signing.signPom(deployment) + } + + repository(url: getRepositoryUrl()) { + authentication( + userName: getRepositoryUsername(), + password: getRepositoryPassword()) + + } + + pom.project { + name POM_NAME + artifactId POM_ARTIFACT_ID + packaging POM_PACKAGING + description POM_DESCRIPTION + url 'https://github.com/facebookincubator/TextLayoutBuilder' + + scm { + url 'https://github.com/facebookincubator/TextLayoutBuilder.git' + connection 'scm:git:https://github.com/facebookincubator/TextLayoutBuilder.git' + developerConnection 'scm:git:git@github.com:facebookincubator/TextLayoutBuilder.git' + } + + licenses { + license { + name 'BSD License' + url 'https://github.com/facebookincubator/TextLayoutBuilder/blob/master/LICENSE' + distribution 'repo' + } + } + + developers { + developer { + id 'facebook' + name 'Facebook' + } + } + } + } + } +} diff --git a/release.gradle b/release.gradle new file mode 100644 index 0000000..1dab987 --- /dev/null +++ b/release.gradle @@ -0,0 +1,5 @@ +// Common Android tasks for all releases that generate Javadocs, sources, etc. +apply from: rootProject.file('gradle/android-tasks.gradle') + +// Upload to Sonatype +apply from: rootProject.file('release-sonatype.gradle') diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..2b9704b --- /dev/null +++ b/settings.gradle @@ -0,0 +1,4 @@ +include ':library' +include ':library:libs:proxy' +include ':library:libs:android-text' +include ':library:libs:android-support-v4-text'