Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port original google TypefaceSpan code for backwards compatibility #2432

Merged
merged 3 commits into from Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Expand Up @@ -752,7 +752,7 @@ task jacocoTestReport(type:JacocoReport, dependsOn: "testCurrentDebugUnitTest")
// see https://marathonlabs.github.io/marathon/doc/configuration.html
marathon {
uncompletedTestRetryQuota = 3
testOutputTimeoutMillis = 360000
testOutputTimeoutMillis = 720000
poolingStrategy {
operatingSystem = true
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/assets/LICENSE.txt
Expand Up @@ -59,6 +59,8 @@ The GeoJson and Turf classes are Copyright (c) 2016 Mapbox licensed on MIT terms

The google gson library is licensed under the Apache License, version 2.

TypefaceSpanCompat and LeakyTypefaceStorage are derived from the corresponding AOSP code licensed under the Apache License, version 2.

Roulette Wheel by Bakunetsu Kaito from the Noun Project

The material design lightbulb from Austin Andrews SIL Open Font License 1.1
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/de/blau/android/DisambiguationMenu.java
Expand Up @@ -12,7 +12,6 @@
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.text.style.TypefaceSpan;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
Expand Down Expand Up @@ -41,6 +40,7 @@
import de.blau.android.tasks.Todo;
import de.blau.android.util.Screen;
import de.blau.android.util.ThemeUtils;
import de.blau.android.util.TypefaceSpanCompat;
import de.blau.android.util.Util;

/**
Expand All @@ -66,7 +66,7 @@ public interface OnMenuItemClickListener {
private final List<OnMenuItemClickListener> onClickListeners = new ArrayList<>();
private View header;
private boolean rtl;
private final TypefaceSpan monospaceSpan;
private final TypefaceSpanCompat monospaceSpan;

/**
* Create a new menu to disambiguate between nearby objects
Expand All @@ -76,7 +76,7 @@ public interface OnMenuItemClickListener {
public DisambiguationMenu(@NonNull View anchor) {
this.anchor = anchor;
rtl = Util.isRtlScript(anchor.getContext());
monospaceSpan = new TypefaceSpan(ResourcesCompat.getFont(anchor.getContext(), R.font.b612mono));
monospaceSpan = new TypefaceSpanCompat(ResourcesCompat.getFont(anchor.getContext(), R.font.b612mono));
}

/**
Expand Down
86 changes: 86 additions & 0 deletions src/main/java/de/blau/android/util/LeakyTypefaceStorage.java
@@ -0,0 +1,86 @@
/* Port for backwards compatible version of TextfaceSpan for pre-Android 9 */
/*
* Copyright (C) 2017 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 de.blau.android.util;

import android.graphics.Typeface;
import android.os.Parcel;
import android.os.Process;
import android.util.ArrayMap;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.ArrayList;

/**
* This class is used for Parceling Typeface object. Note: Typeface object can not be passed over the process boundary.
*
* @hide
*/
public class LeakyTypefaceStorage {
private static final Object sLock = new Object();
private static final ArrayList<Typeface> sStorage = new ArrayList<>();
private static final ArrayMap<Typeface, Integer> sTypefaceMap = new ArrayMap<>();


/**
* Private constructor to stop instantiation
*/
private LeakyTypefaceStorage() {
// nothing
}

/**
* Write typeface to parcel.
*
* You can't transfer Typeface to a different process. {@link readTypefaceFromParcel} will return {@code null} if
* the {@link readTypefaceFromParcel} is called in a different process.
*
* @param typeface A {@link Typeface} to be written.
* @param parcel A {@link Parcel} object.
*/
public static void writeTypefaceToParcel(@Nullable Typeface typeface, @NonNull Parcel parcel) {
parcel.writeInt(Process.myPid());
synchronized (sLock) {
final int id;
final Integer i = sTypefaceMap.get(typeface);
if (i != null) {
id = i.intValue();
} else {
id = sStorage.size();
sStorage.add(typeface);
sTypefaceMap.put(typeface, id);
}
parcel.writeInt(id);
}
}

/**
* Read typeface from parcel.
*
* If the {@link Typeface} was created in another process, this method returns null.
*
* @param parcel A {@link Parcel} object
* @return A {@link Typeface} object.
*/
public static @androidx.annotation.Nullable Typeface readTypefaceFromParcel(@androidx.annotation.NonNull Parcel parcel) {
final int pid = parcel.readInt();
final int typefaceId = parcel.readInt();
if (pid != Process.myPid()) {
return null; // The Typeface was created and written in another process.
}
synchronized (sLock) {
return sStorage.get(typefaceId);
}
}
}
164 changes: 164 additions & 0 deletions src/main/java/de/blau/android/util/TypefaceSpanCompat.java
@@ -0,0 +1,164 @@
/* Port for backwards compatible version of TextfaceSpan for pre-Android 9 */
/*
* Copyright (C) 2006 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 de.blau.android.util;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.Parcel;
import android.text.ParcelableSpan;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;

/**
* Span that updates the typeface of the text it's attached to. The <code>TypefaceSpan</code> can be constructed either
* based on a font family or based on a <code>Typeface</code>. When {@link #TypefaceSpan(String)} is used, the previous
* style of the <code>TextView</code> is kept. When {@link #TypefaceSpan(Typeface)} is used, the <code>Typeface</code>
* style replaces the <code>TextView</code>'s style.
* <p>
* For example, let's consider a <code>TextView</code> with <code>android:textStyle="italic"</code> and a typeface
* created based on a font from resources, with a bold style. When applying a <code>TypefaceSpan</code> based the
* typeface, the text will only keep the bold style, overriding the <code>TextView</code>'s textStyle. When applying a
* <code>TypefaceSpan</code> based on a font family: "monospace", the resulted text will keep the italic style.
*
* <pre>
* Typeface myTypeface = Typeface.create(ResourcesCompat.getFont(context, R.font.acme), Typeface.BOLD);
* SpannableString string = new SpannableString("Text with typeface span.");
* string.setSpan(new TypefaceSpan(myTypeface), 10, 18, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
* string.setSpan(new TypefaceSpan("monospace"), 19, 22, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
* </pre>
*
* <img src="{@docRoot}reference/android/images/text/style/typefacespan.png" /> <figcaption>Text with
* <code>TypefaceSpan</code>s constructed based on a font from resource and from a font family.</figcaption>
*/
public class TypefaceSpanCompat extends MetricAffectingSpan implements ParcelableSpan {
@Nullable
private final String mFamily;
@Nullable
private final Typeface mTypeface;

/**
* Constructs a {@link TypefaceSpan} based on the font family. The previous style of the TextPaint is kept. If the
* font family is null, the text paint is not modified.
*
* @param family The font family for this typeface. Examples include "monospace", "serif", and "sans-serif"
*/
public TypefaceSpanCompat(@Nullable String family) {
this(family, null);
}

/**
* Constructs a {@link TypefaceSpan} from a {@link Typeface}. The previous style of the TextPaint is overridden and
* the style of the typeface is used.
*
* @param typeface the typeface
*/
public TypefaceSpanCompat(@NonNull Typeface typeface) {
this(null, typeface);
}

/**
* Constructs a {@link TypefaceSpan} from a parcel.
*/
public TypefaceSpanCompat(@NonNull Parcel src) {
mFamily = src.readString();
mTypeface = LeakyTypefaceStorage.readTypefaceFromParcel(src);
}

private TypefaceSpanCompat(@Nullable String family, @Nullable Typeface typeface) {
mFamily = family;
mTypeface = typeface;
}

@Override
public int getSpanTypeId() {
return 13 /* android.text.TextUtils.TYPEFACE_SPAN */;
}

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mFamily);
LeakyTypefaceStorage.writeTypefaceToParcel(mTypeface, dest);
}

/**
* Returns the font family name set in the span.
*
* @return the font family name
* @see #TypefaceSpan(String)
*/
@Nullable
public String getFamily() {
return mFamily;
}

/**
* Returns the typeface set in the span.
*
* @return the typeface set
* @see #TypefaceSpan(Typeface)
*/
@Nullable
public Typeface getTypeface() {
return mTypeface;
}

@Override
public void updateDrawState(@NonNull TextPaint ds) {
updateTypeface(ds);
}

@Override
public void updateMeasureState(@NonNull TextPaint paint) {
updateTypeface(paint);
}

private void updateTypeface(@NonNull Paint paint) {
if (mTypeface != null) {
paint.setTypeface(mTypeface);
} else if (mFamily != null) {
applyFontFamily(paint, mFamily);
}
}

private void applyFontFamily(@NonNull Paint paint, @NonNull String family) {
int style;
Typeface old = paint.getTypeface();
if (old == null) {
style = Typeface.NORMAL;
} else {
style = old.getStyle();
}
final Typeface styledTypeface = Typeface.create(family, style);
int fake = style & ~styledTypeface.getStyle();
if ((fake & Typeface.BOLD) != 0) {
paint.setFakeBoldText(true);
}
if ((fake & Typeface.ITALIC) != 0) {
paint.setTextSkewX(-0.25f);
}
paint.setTypeface(styledTypeface);
}

@Override
public String toString() {
return "TypefaceSpan{" + "family='" + getFamily() + '\'' + ", typeface=" + getTypeface() + '}';
}
}