投稿のHTMLデータの表示を少し改善
This commit is contained in:
parent
a383ca0657
commit
c24aeeadc4
|
@ -401,6 +401,8 @@ class TootInstance(parser: TootParser, src: JsonObject) {
|
|||
*/
|
||||
private val reOldMisskeyCompatible = """\A[\d.]+:compatible:misskey:""".toRegex()
|
||||
|
||||
private val reBothCompatible = """\b(?:misskey|calckey)\b""".toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// 疑似アカウントの追加時に、インスタンスの検証を行う
|
||||
private suspend fun TootApiClient.getInstanceInformation(
|
||||
forceAccessToken: String? = null,
|
||||
|
@ -419,7 +421,9 @@ class TootInstance(parser: TootParser, src: JsonObject) {
|
|||
// 他、Mastodonではない場合は kids.0px.io が存在する
|
||||
// https://kids.0px.io/notes/9b628dpesb
|
||||
// Misskey有効トグルで結果を切り替えたいらしい
|
||||
version.contains("misskey", ignoreCase = true) &&
|
||||
//
|
||||
// Calckey.jp は調査中
|
||||
reBothCompatible.containsMatchIn(version) &&
|
||||
PrefB.bpEnableDeprecatedSomething.value -> Unit
|
||||
|
||||
// 両方のAPIに応答するサーバは他にないと思う。
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package jp.juggler.subwaytooter.mfm
|
||||
|
||||
import android.graphics.Typeface
|
||||
import jp.juggler.subwaytooter.span.BlockCodeSpan
|
||||
import jp.juggler.subwaytooter.span.BlockQuoteSpan
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
import jp.juggler.util.data.encodePercent
|
||||
import jp.juggler.util.data.notEmpty
|
||||
|
@ -51,7 +54,7 @@ enum class NodeType(val render: SpanOutputEnv.(Node) -> Unit) {
|
|||
spanList.addLast(start, sb.length, android.text.style.BackgroundColorSpan(0x40808080))
|
||||
spanList.addLast(
|
||||
start, sb.length,
|
||||
fontSpan(android.graphics.Typeface.MONOSPACE)
|
||||
fontSpan(Typeface.MONOSPACE)
|
||||
)
|
||||
}
|
||||
}),
|
||||
|
@ -72,12 +75,8 @@ enum class NodeType(val render: SpanOutputEnv.(Node) -> Unit) {
|
|||
val sp = MisskeySyntaxHighlighter.parse(text)
|
||||
appendText(text)
|
||||
spanList.addWithOffset(sp, start)
|
||||
spanList.addLast(start, sb.length, android.text.style.BackgroundColorSpan(0x40808080))
|
||||
spanList.addLast(start, sb.length, android.text.style.RelativeSizeSpan(0.7f))
|
||||
spanList.addLast(
|
||||
start, sb.length,
|
||||
fontSpan(android.graphics.Typeface.MONOSPACE)
|
||||
)
|
||||
|
||||
spanList.addLast(start, sb.length, BlockCodeSpan())
|
||||
closeBlock()
|
||||
}
|
||||
}),
|
||||
|
@ -96,7 +95,7 @@ enum class NodeType(val render: SpanOutputEnv.(Node) -> Unit) {
|
|||
spanList.addLast(
|
||||
start,
|
||||
sb.length,
|
||||
fontSpan(android.graphics.Typeface.defaultFromStyle(android.graphics.Typeface.ITALIC))
|
||||
fontSpan(Typeface.defaultFromStyle(Typeface.ITALIC))
|
||||
)
|
||||
}
|
||||
}),
|
||||
|
@ -189,7 +188,7 @@ enum class NodeType(val render: SpanOutputEnv.(Node) -> Unit) {
|
|||
fireRenderChildNodes(it)
|
||||
spanList.addLast(
|
||||
start, sb.length,
|
||||
fontSpan(android.graphics.Typeface.defaultFromStyle(android.graphics.Typeface.ITALIC))
|
||||
fontSpan(Typeface.defaultFromStyle(Typeface.ITALIC))
|
||||
)
|
||||
}
|
||||
}),
|
||||
|
@ -310,34 +309,21 @@ enum class NodeType(val render: SpanOutputEnv.(Node) -> Unit) {
|
|||
|
||||
val bgColor =
|
||||
MisskeyMarkdownDecoder.quoteNestColors[it.quoteNest % MisskeyMarkdownDecoder.quoteNestColors.size]
|
||||
// TextView の文字装飾では「ブロック要素の入れ子」を表現できない
|
||||
// 内容の各行の始端に何か追加するというのがまずキツい
|
||||
// しかし各行の頭に引用マークをつけないと引用のネストで意味が通じなくなってしまう
|
||||
val tmp = sb.toString()
|
||||
//log.d("QUOTE_BLOCK tmp=${tmp} start=$start end=${tmp.length}")
|
||||
for (i in tmp.length - 1 downTo start) {
|
||||
val prevChar = when (i) {
|
||||
start -> '\n'
|
||||
else -> tmp[i - 1]
|
||||
}
|
||||
//log.d("QUOTE_BLOCK prevChar=${ String.format("%x",prevChar.toInt())}")
|
||||
if (prevChar == '\n') {
|
||||
//log.d("QUOTE_BLOCK insert! i=$i")
|
||||
sb.insert(i, "> ")
|
||||
spanList.insert(i, 2)
|
||||
spanList.addLast(
|
||||
i, i + 1,
|
||||
android.text.style.BackgroundColorSpan(bgColor)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
spanList.addLast(
|
||||
start,
|
||||
sb.length,
|
||||
fontSpan(android.graphics.Typeface.defaultFromStyle(android.graphics.Typeface.ITALIC))
|
||||
BlockQuoteSpan(context = context, blockQuoteColor = bgColor)
|
||||
)
|
||||
spanList.addLast(
|
||||
start,
|
||||
sb.length,
|
||||
fontSpan(
|
||||
Typeface.defaultFromStyle(
|
||||
Typeface.ITALIC
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
closeBlock()
|
||||
}
|
||||
}),
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package jp.juggler.subwaytooter.span
|
||||
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.Typeface
|
||||
import android.text.Layout
|
||||
import android.text.TextPaint
|
||||
import android.text.style.LeadingMarginSpan
|
||||
import android.text.style.MetricAffectingSpan
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.pref.lazyContext
|
||||
import jp.juggler.util.ui.attrColor
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* コードブロック用の装飾スパン
|
||||
*/
|
||||
class BlockCodeSpan (
|
||||
context: Context = lazyContext,
|
||||
private var typeface: Typeface = Typeface.MONOSPACE,
|
||||
private var relativeTextSize: Float = 0.7f,
|
||||
private var margin: Int = 0,
|
||||
private var textColor: Int = context.attrColor(R.attr.colorTextContent),
|
||||
private var backgroundColor: Int = 0x40808080,
|
||||
) : MetricAffectingSpan(), LeadingMarginSpan {
|
||||
|
||||
private val rect = Rect()
|
||||
private val paint = Paint()
|
||||
|
||||
private fun apply(paint: TextPaint) {
|
||||
paint.color = textColor
|
||||
paint.typeface = typeface
|
||||
paint.textSize = paint.textSize * relativeTextSize
|
||||
}
|
||||
|
||||
override fun updateDrawState(tp: TextPaint) {
|
||||
apply(tp)
|
||||
}
|
||||
|
||||
override fun updateMeasureState(textPaint: TextPaint) {
|
||||
apply(textPaint)
|
||||
}
|
||||
|
||||
override fun getLeadingMargin(first: Boolean): Int {
|
||||
return margin
|
||||
}
|
||||
|
||||
override fun drawLeadingMargin(
|
||||
c: Canvas,
|
||||
p: Paint,
|
||||
x: Int,
|
||||
dir: Int,
|
||||
top: Int,
|
||||
baseline: Int,
|
||||
bottom: Int,
|
||||
text: CharSequence?,
|
||||
start: Int,
|
||||
end: Int,
|
||||
first: Boolean,
|
||||
layout: Layout
|
||||
) {
|
||||
paint.style = Paint.Style.FILL
|
||||
paint.color = backgroundColor
|
||||
|
||||
val line = layout.getLineForOffset(start)
|
||||
val left = layout.getParagraphLeft(line)
|
||||
val right = layout.getParagraphRight(line)
|
||||
rect.set(
|
||||
min(left,right),
|
||||
top,
|
||||
max(left,right),
|
||||
bottom,
|
||||
)
|
||||
c.drawRect(rect, paint)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package jp.juggler.subwaytooter.span
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.text.Layout
|
||||
import android.text.style.LeadingMarginSpan
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.util.ui.attrColor
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* ブロック引用の装飾スパン
|
||||
*/
|
||||
class BlockQuoteSpan(
|
||||
context: Context,
|
||||
private val margin: Int = (context.resources.displayMetrics.density * 24f + 0.5f).toInt(),
|
||||
private val quoteWidth: Int = (context.resources.displayMetrics.density * 4f + 0.5f).toInt(),
|
||||
private val blockQuoteColor: Int = context.attrColor(R.attr.colorTextHint),
|
||||
) : LeadingMarginSpan {
|
||||
private val rect = Rect()
|
||||
private val paint = Paint()
|
||||
override fun getLeadingMargin(first: Boolean): Int = margin
|
||||
|
||||
override fun drawLeadingMargin(
|
||||
c: Canvas,
|
||||
p: Paint,
|
||||
x: Int,
|
||||
dir: Int,
|
||||
top: Int,
|
||||
baseline: Int,
|
||||
bottom: Int,
|
||||
text: CharSequence,
|
||||
start: Int,
|
||||
end: Int,
|
||||
first: Boolean,
|
||||
layout: Layout,
|
||||
) {
|
||||
paint.set(p)
|
||||
paint.style = Paint.Style.FILL
|
||||
paint.color = blockQuoteColor
|
||||
|
||||
val line = layout.getLineForOffset(start)
|
||||
val edge = if (dir > 0) {
|
||||
layout.getParagraphLeft(line)
|
||||
} else {
|
||||
layout.getParagraphRight(line)
|
||||
}
|
||||
val width = quoteWidth
|
||||
val l = edge - dir * width
|
||||
val r = edge - dir * width * 2
|
||||
rect.set(
|
||||
min(l, r),
|
||||
top,
|
||||
max(l, r),
|
||||
bottom
|
||||
)
|
||||
c.drawRect(rect, paint)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package jp.juggler.subwaytooter.span
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.text.Layout
|
||||
import android.text.style.LeadingMarginSpan
|
||||
|
||||
/**
|
||||
* dd 要素の字下げスパン
|
||||
*/
|
||||
class DdSpan(
|
||||
context: Context,
|
||||
marginDp: Float = 24f,
|
||||
) : LeadingMarginSpan {
|
||||
private val marginPx = (context.resources.displayMetrics.density * marginDp + 0.5f).toInt()
|
||||
|
||||
override fun getLeadingMargin(first: Boolean): Int = marginPx
|
||||
|
||||
override fun drawLeadingMargin(
|
||||
c: Canvas,
|
||||
p: Paint,
|
||||
x: Int,
|
||||
dir: Int,
|
||||
top: Int,
|
||||
baseline: Int,
|
||||
bottom: Int,
|
||||
text: CharSequence,
|
||||
start: Int,
|
||||
end: Int,
|
||||
first: Boolean,
|
||||
layout: Layout,
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package jp.juggler.subwaytooter.span
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.text.Layout
|
||||
import android.text.TextPaint
|
||||
import android.text.style.LeadingMarginSpan
|
||||
import android.text.style.MetricAffectingSpan
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.pref.lazyContext
|
||||
import jp.juggler.util.ui.attrColor
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* コードブロック用の装飾スパン
|
||||
*/
|
||||
class HrSpan(
|
||||
context: Context = lazyContext,
|
||||
private var lineColor: Int = context.attrColor(R.attr.colorTextContent),
|
||||
private var lineHeight: Int = (context.resources.displayMetrics.density * 1f + 0.5f).toInt(),
|
||||
) : MetricAffectingSpan(), LeadingMarginSpan {
|
||||
|
||||
private val rect = Rect()
|
||||
private val paint = Paint()
|
||||
|
||||
private fun apply(paint: TextPaint) {
|
||||
paint.color = 0
|
||||
}
|
||||
|
||||
override fun updateDrawState(tp: TextPaint) {
|
||||
apply(tp)
|
||||
}
|
||||
|
||||
override fun updateMeasureState(textPaint: TextPaint) {
|
||||
apply(textPaint)
|
||||
}
|
||||
|
||||
override fun getLeadingMargin(first: Boolean): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun drawLeadingMargin(
|
||||
c: Canvas,
|
||||
p: Paint,
|
||||
x: Int,
|
||||
dir: Int,
|
||||
top: Int,
|
||||
baseline: Int,
|
||||
bottom: Int,
|
||||
text: CharSequence?,
|
||||
start: Int,
|
||||
end: Int,
|
||||
first: Boolean,
|
||||
layout: Layout,
|
||||
) {
|
||||
paint.style = Paint.Style.FILL
|
||||
paint.color = lineColor
|
||||
|
||||
val lineY = (top + bottom).shr(1)
|
||||
val lineT = lineY - lineHeight.shr(1)
|
||||
val lineB = lineT + lineHeight
|
||||
|
||||
val line = layout.getLineForOffset(start)
|
||||
val left = layout.getParagraphLeft(line)
|
||||
val right = layout.getParagraphRight(line)
|
||||
rect.set(min(left, right), lineT, max(left, right), lineB)
|
||||
c.drawRect(rect, paint)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package jp.juggler.subwaytooter.span
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.text.TextPaint
|
||||
import io.github.inflationx.calligraphy3.CalligraphyTypefaceSpan
|
||||
|
||||
class InlineCodeSpan(
|
||||
tf: Typeface =Typeface.MONOSPACE,
|
||||
private val bgColor :Int = 0x40808080,
|
||||
) : CalligraphyTypefaceSpan(tf) {
|
||||
override fun updateDrawState(drawState: TextPaint) {
|
||||
super.updateDrawState(drawState)
|
||||
drawState.bgColor = bgColor
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package jp.juggler.subwaytooter.span
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.text.Layout
|
||||
import android.text.TextPaint
|
||||
import android.text.style.LeadingMarginSpan
|
||||
|
||||
class OrderedListItemSpan(
|
||||
private val order: String,
|
||||
orders: List<String>,
|
||||
) : LeadingMarginSpan {
|
||||
companion object {
|
||||
fun List<String>.longest(paint: TextPaint): String? {
|
||||
var longestText: String? = null
|
||||
var longestTextWidth: Int? = null
|
||||
forEach { text ->
|
||||
val textWidth = (paint.measureText(text) + .5f).toInt()
|
||||
if (longestTextWidth?.takeIf { it > textWidth } != null) return@forEach
|
||||
longestText = text
|
||||
longestTextWidth = textWidth
|
||||
}
|
||||
return longestText
|
||||
}
|
||||
}
|
||||
|
||||
private val paint = TextPaint()
|
||||
private val longestOrder = orders.longest(paint) ?: ""
|
||||
private var longestOrderWidth =
|
||||
(paint.measureText(longestOrder) + .5f).toInt() +
|
||||
(paint.measureText(". ") * 2f + .5f).toInt()
|
||||
|
||||
override fun getLeadingMargin(first: Boolean): Int = longestOrderWidth
|
||||
|
||||
override fun drawLeadingMargin(
|
||||
c: Canvas,
|
||||
p: Paint,
|
||||
x: Int,
|
||||
dir: Int,
|
||||
top: Int,
|
||||
baseline: Int,
|
||||
bottom: Int,
|
||||
text: CharSequence,
|
||||
start: Int,
|
||||
end: Int,
|
||||
first: Boolean,
|
||||
layout: Layout,
|
||||
) {
|
||||
// if there was a line break, we don't need to draw anything
|
||||
if (!first || !selfStart(start, text, this)) {
|
||||
return
|
||||
}
|
||||
|
||||
paint.set(p)
|
||||
|
||||
val longestOrderWidth = (paint.measureText(longestOrder) + .5f).toInt() +
|
||||
(paint.measureText(".") * 2f + .5f).toInt()
|
||||
this.longestOrderWidth = longestOrderWidth
|
||||
|
||||
val line = layout.getLineForOffset(start)
|
||||
if (dir > 0) {
|
||||
paint.textAlign = Paint.Align.LEFT
|
||||
val left = layout.getParagraphLeft(line) - longestOrderWidth
|
||||
c.drawText("${order}.", left.toFloat(), baseline.toFloat(), paint)
|
||||
} else {
|
||||
paint.textAlign = Paint.Align.RIGHT
|
||||
val right = layout.getParagraphRight(line) + longestOrderWidth
|
||||
c.drawText(".${order}", right.toFloat(), baseline.toFloat(), paint)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package jp.juggler.subwaytooter.span
|
||||
|
||||
import android.text.Spanned
|
||||
|
||||
fun selfStart(start: Int, text: CharSequence?, span: Any?): Boolean {
|
||||
return text is Spanned && text.getSpanStart(span) == start
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package jp.juggler.subwaytooter.span
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.text.Layout
|
||||
import android.text.TextPaint
|
||||
import android.text.style.LeadingMarginSpan
|
||||
import androidx.annotation.IntRange
|
||||
|
||||
class UnorderedListItemSpan(
|
||||
@IntRange(from = 0) private val level: Int,
|
||||
private var bulletWidth: Int = 0,
|
||||
) : LeadingMarginSpan {
|
||||
|
||||
private val circle = RectF()
|
||||
private val rectangle = Rect()
|
||||
private val paint = TextPaint()
|
||||
|
||||
private var marginWidth = (paint.descent() - paint.ascent() + .5f).toInt()
|
||||
|
||||
override fun getLeadingMargin(first: Boolean) = marginWidth
|
||||
|
||||
override fun drawLeadingMargin(
|
||||
c: Canvas,
|
||||
p: Paint,
|
||||
x: Int,
|
||||
dir: Int,
|
||||
top: Int,
|
||||
baseline: Int,
|
||||
bottom: Int,
|
||||
text: CharSequence,
|
||||
start: Int,
|
||||
end: Int,
|
||||
first: Boolean,
|
||||
layout: Layout,
|
||||
) {
|
||||
// if there was a line break, we don't need to draw anything
|
||||
if (!first || !selfStart(start, text, this)) return
|
||||
|
||||
paint.set(p)
|
||||
val save = c.save()
|
||||
try {
|
||||
// テキストの高さ
|
||||
val textLineHeight = (paint.descent() - paint.ascent() + .5f).toInt()
|
||||
// ドットを含む横マージンの幅
|
||||
@Suppress("UnnecessaryVariable")
|
||||
val marginWidth = textLineHeight
|
||||
this.marginWidth = marginWidth
|
||||
val marginHalf = marginWidth/2
|
||||
|
||||
// ドットの直径
|
||||
val bulletWidth = textLineHeight / 2
|
||||
|
||||
// 行頭からドットの開始端までの距離
|
||||
val marginLeft = (marginWidth - bulletWidth) / 2
|
||||
|
||||
val line = layout.getLineForOffset(start)
|
||||
val edge = if (dir > 0) {
|
||||
layout.getParagraphLeft(line)
|
||||
} else {
|
||||
layout.getParagraphRight(line)
|
||||
}
|
||||
val l = edge - dir * marginHalf - bulletWidth/2
|
||||
val t =
|
||||
baseline + ((paint.descent() + paint.ascent()) / 2f + .5f).toInt() - bulletWidth / 2
|
||||
val r = l + bulletWidth
|
||||
val b = t + bulletWidth
|
||||
if (level == 0 || level == 1) {
|
||||
circle.set(l.toFloat(), t.toFloat(), r.toFloat(), b.toFloat())
|
||||
paint.style = when (level) {
|
||||
0 -> Paint.Style.FILL
|
||||
else -> Paint.Style.STROKE
|
||||
}
|
||||
c.drawOval(circle, paint)
|
||||
} else {
|
||||
rectangle.set(l, t, r, b)
|
||||
paint.style = Paint.Style.FILL
|
||||
c.drawRect(rectangle, paint)
|
||||
}
|
||||
} finally {
|
||||
c.restoreToCount(save)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ import jp.juggler.subwaytooter.api.entity.TootMention
|
|||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.mfm.MisskeyMarkdownDecoder
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.pref.lazyContext
|
||||
import jp.juggler.subwaytooter.span.*
|
||||
import jp.juggler.subwaytooter.table.HighlightWord
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
|
@ -321,13 +322,15 @@ object HTMLDecoder {
|
|||
val nestLevelDefinition: Int,
|
||||
val nestLevelQuote: Int,
|
||||
var order: Int = 0,
|
||||
val listOrders: List<String>? = null,
|
||||
) {
|
||||
fun subOrdered() = ListContext(
|
||||
fun subOrdered(listOrders:List<String>) = ListContext(
|
||||
type = ListType.Ordered,
|
||||
nestLevelOrdered + 1,
|
||||
nestLevelUnordered,
|
||||
nestLevelDefinition,
|
||||
nestLevelQuote
|
||||
nestLevelQuote,
|
||||
listOrders= listOrders,
|
||||
)
|
||||
|
||||
fun subUnordered() = ListContext(
|
||||
|
@ -354,12 +357,7 @@ object HTMLDecoder {
|
|||
nestLevelQuote + 1
|
||||
)
|
||||
|
||||
fun increment() = when (type) {
|
||||
ListType.Ordered -> "${++order}. "
|
||||
ListType.Unordered -> "${listMarkers[nestLevelUnordered % listMarkers.size]} "
|
||||
ListType.Definition -> ""
|
||||
else -> ""
|
||||
}
|
||||
fun increment() = ++order
|
||||
|
||||
fun inList() = nestLevelOrdered + nestLevelUnordered + nestLevelDefinition > 0
|
||||
|
||||
|
@ -369,43 +367,6 @@ object HTMLDecoder {
|
|||
}
|
||||
}
|
||||
|
||||
// SpannableStringBuilderを行ごとに分解する
|
||||
// 行末の改行文字は各行の末尾に残る
|
||||
// 最終行の長さが0(改行文字もなし)だった場合は出力されない
|
||||
fun SpannableStringBuilder.splitLines() =
|
||||
ArrayList<SpannableStringBuilder>().also { dst ->
|
||||
// 入力の末尾のtrim
|
||||
var end = this.length
|
||||
while (end > 0 && CharacterGroup.isWhitespace(this[end - 1].code)) --end
|
||||
|
||||
// 入力の最初の非空白文字の位置を調べておく
|
||||
var firstNonSpace = 0
|
||||
while (firstNonSpace < end && CharacterGroup.isWhitespace(this[firstNonSpace].code)) ++firstNonSpace
|
||||
|
||||
var i = 0
|
||||
while (i < end) {
|
||||
val lineStart = i
|
||||
while (i < end && this[i] != '\n') ++i
|
||||
val lineEnd = if (i >= end) end else i + 1
|
||||
++i
|
||||
|
||||
// 行頭の空白を削る
|
||||
// while (lineStart < lineEnd &&
|
||||
// this[lineStart] != '\n' &&
|
||||
// CharacterGroup.isWhitespace(this[lineStart].toInt())
|
||||
// ) ++lineStart
|
||||
|
||||
// 最初の非空白文字以降の行を出力する
|
||||
if (lineEnd > firstNonSpace) {
|
||||
dst.add(this.subSequence(lineStart, lineEnd) as SpannableStringBuilder)
|
||||
}
|
||||
}
|
||||
if (dst.isEmpty()) {
|
||||
// ブロック要素は最低1行は存在するので、1行だけの要素を作る
|
||||
dst.add(SpannableStringBuilder())
|
||||
}
|
||||
}
|
||||
|
||||
private val reLastLine = """(?:\A|\n)([^\n]*)\z""".toRegex()
|
||||
|
||||
private class Node {
|
||||
|
@ -413,7 +374,7 @@ object HTMLDecoder {
|
|||
val child_nodes = ArrayList<Node>()
|
||||
|
||||
val tag: String
|
||||
val text: String
|
||||
var text: String
|
||||
|
||||
private val href: String?
|
||||
get() {
|
||||
|
@ -569,11 +530,13 @@ object HTMLDecoder {
|
|||
class EncodeSpanEnv(
|
||||
val options: DecodeOptions,
|
||||
val listContext: ListContext,
|
||||
val tag: String,
|
||||
val node: Node,
|
||||
val sb: SpannableStringBuilder,
|
||||
val sbTmp: SpannableStringBuilder,
|
||||
val spanStart: Int,
|
||||
)
|
||||
){
|
||||
val tag = node.tag
|
||||
}
|
||||
|
||||
val originalFlusher: EncodeSpanEnv.() -> Unit = {
|
||||
when (tag) {
|
||||
|
@ -692,41 +655,31 @@ object HTMLDecoder {
|
|||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
"pre" -> {
|
||||
sb.setSpan(
|
||||
BackgroundColorSpan(0x40808080),
|
||||
spanStart,
|
||||
sb.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
sb.setSpan(
|
||||
RelativeSizeSpan(0.7f),
|
||||
spanStart,
|
||||
sb.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
sb.setSpan(
|
||||
fontSpan(Typeface.MONOSPACE),
|
||||
spanStart,
|
||||
sb.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
"code" -> {
|
||||
// インラインコード用の装飾
|
||||
// sb.setSpan(
|
||||
// BackgroundColorSpan(0x40808080),
|
||||
// spanStart,
|
||||
// sb.length,
|
||||
// Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
// )
|
||||
sb.setSpan(
|
||||
BackgroundColorSpan(0x40808080),
|
||||
spanStart,
|
||||
sb.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
sb.setSpan(
|
||||
fontSpan(Typeface.MONOSPACE),
|
||||
InlineCodeSpan(),
|
||||
spanStart,
|
||||
sb.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
"hr" -> {
|
||||
val start =sb.length
|
||||
sb.append("-")
|
||||
sb.setSpan(
|
||||
HrSpan(lazyContext),
|
||||
spanStart,
|
||||
sb.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
"hr" -> sb.append("----------")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -736,6 +689,17 @@ object HTMLDecoder {
|
|||
for (tag in tags) this[tag] = block
|
||||
}
|
||||
|
||||
fun SpannableStringBuilder.deleteLastSpaces() {
|
||||
// 最低でも1文字は残す
|
||||
var last = length - 1
|
||||
while (last > 0) {
|
||||
if (!CharacterGroup.isWhitespace(get(last).code)) break
|
||||
--last
|
||||
}
|
||||
// 末尾の空白を除去
|
||||
if (last != length - 1) delete(last + 1, length)
|
||||
}
|
||||
|
||||
add("a") {
|
||||
val linkInfo = formatLinkCaption(options, sbTmp, href ?: "")
|
||||
val caption = linkInfo.caption
|
||||
|
@ -780,67 +744,115 @@ object HTMLDecoder {
|
|||
}
|
||||
|
||||
add("blockquote") {
|
||||
val bg_color = listContext.quoteColor()
|
||||
|
||||
// TextView の文字装飾では「ブロック要素の入れ子」を表現できない
|
||||
// 内容の各行の始端に何か追加するというのがまずキツい
|
||||
// しかし各行の頭に引用マークをつけないと引用のネストで意味が通じなくなってしまう
|
||||
|
||||
val startItalic = sb.length
|
||||
sbTmp.splitLines().forEach { line ->
|
||||
val lineStart = sb.length
|
||||
sb.append("> ")
|
||||
val start = sb.length
|
||||
sbTmp.deleteLastSpaces()
|
||||
sb.append(sbTmp)
|
||||
sb.setSpan(
|
||||
BackgroundColorSpan(bg_color),
|
||||
lineStart,
|
||||
lineStart + 1,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
BlockQuoteSpan(
|
||||
context = lazyContext,
|
||||
blockQuoteColor = listContext.quoteColor()
|
||||
),
|
||||
start,
|
||||
sb.length,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
|
||||
)
|
||||
sb.append(line)
|
||||
}
|
||||
sb.setSpan(
|
||||
fontSpan(Typeface.defaultFromStyle(Typeface.ITALIC)),
|
||||
startItalic,
|
||||
start,
|
||||
sb.length,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
|
||||
)
|
||||
}
|
||||
add("pre") {
|
||||
val start = sb.length
|
||||
sbTmp.deleteLastSpaces()
|
||||
// インラインコード用の装飾を除去する
|
||||
sbTmp.getSpans(0, sbTmp.length, Any::class.java).forEach { span ->
|
||||
if (span is BackgroundColorSpan && span.backgroundColor == 0x40808080) {
|
||||
sbTmp.removeSpan(span)
|
||||
} else if (span is InlineCodeSpan) {
|
||||
sbTmp.removeSpan(span)
|
||||
}
|
||||
}
|
||||
sb.append(sbTmp)
|
||||
sb.setSpan(
|
||||
BlockCodeSpan(),
|
||||
start,
|
||||
sb.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
|
||||
add("li") {
|
||||
val lineHeader1 = listContext.increment()
|
||||
val lineHeader2 = " ".repeat(lineHeader1.length)
|
||||
sbTmp.splitLines().forEachIndexed { i, line ->
|
||||
sb.append(if (i == 0) lineHeader1 else lineHeader2)
|
||||
sb.append(line)
|
||||
sbTmp.deleteLastSpaces()
|
||||
val start = sb.length
|
||||
sb.append(sbTmp)
|
||||
when (listContext.type) {
|
||||
ListType.Unordered -> {
|
||||
sb.setSpan(
|
||||
UnorderedListItemSpan(
|
||||
level = listContext.nestLevelOrdered,
|
||||
),
|
||||
start,
|
||||
sb.length,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
ListType.Ordered -> {
|
||||
sb.setSpan(
|
||||
OrderedListItemSpan(
|
||||
order = node.text,
|
||||
orders = listContext.listOrders?: listOf(node.text),
|
||||
),
|
||||
start,
|
||||
sb.length,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
else ->Unit
|
||||
}
|
||||
}
|
||||
|
||||
add("dt") {
|
||||
val prefix = listContext.increment()
|
||||
val startBold = sb.length
|
||||
sbTmp.splitLines().forEach { line ->
|
||||
sb.append(prefix)
|
||||
sb.append(line)
|
||||
}
|
||||
sbTmp.deleteLastSpaces()
|
||||
val start = sb.length
|
||||
sb.append(sbTmp)
|
||||
sb.setSpan(
|
||||
DdSpan(lazyContext, marginDp = 3f),
|
||||
start,
|
||||
sb.length,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
sb.setSpan(
|
||||
fontSpan(Typeface.defaultFromStyle(Typeface.BOLD)),
|
||||
startBold,
|
||||
start,
|
||||
sb.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
|
||||
add("dd") {
|
||||
val prefix = listContext.increment() + " "
|
||||
sbTmp.splitLines().forEach { line ->
|
||||
sb.append(prefix)
|
||||
sb.append(line)
|
||||
}
|
||||
sbTmp.deleteLastSpaces()
|
||||
val start = sb.length
|
||||
sb.append(sbTmp)
|
||||
sb.setSpan(
|
||||
DdSpan(lazyContext, marginDp = 24f),
|
||||
start,
|
||||
sb.length,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun childListContext(tag: String, outerContext: ListContext) = when (tag) {
|
||||
"ol" -> outerContext.subOrdered()
|
||||
fun childListContext(node:Node, outerContext: ListContext) = when (node.tag) {
|
||||
"ol" -> {
|
||||
var n = 1
|
||||
val reversed = false
|
||||
val listItems = node.child_nodes.filter { it.tag == "li" }
|
||||
(if(reversed ) listItems.reversed() else listItems).forEach { v ->
|
||||
v.text = (n++).toString()
|
||||
}
|
||||
outerContext.subOrdered(listItems.map { it.text })
|
||||
}
|
||||
"ul" -> outerContext.subUnordered()
|
||||
"dl" -> outerContext.subDefinition()
|
||||
"blockquote" -> outerContext.subQuote()
|
||||
|
@ -881,7 +893,7 @@ object HTMLDecoder {
|
|||
EncodeSpanEnv(
|
||||
options = options,
|
||||
listContext = listContext,
|
||||
tag = tag,
|
||||
node = this,
|
||||
sb = sb,
|
||||
sbTmp = SpannableStringBuilder(),
|
||||
spanStart = 0,
|
||||
|
@ -892,14 +904,14 @@ object HTMLDecoder {
|
|||
EncodeSpanEnv(
|
||||
options = options,
|
||||
listContext = listContext,
|
||||
tag = tag,
|
||||
node = this,
|
||||
sb = sb,
|
||||
sbTmp = sb,
|
||||
spanStart = sb.length
|
||||
)
|
||||
}
|
||||
|
||||
val childListContext = childListContext(tag, listContext)
|
||||
val childListContext = childListContext(this, listContext)
|
||||
|
||||
child_nodes.forEachIndexed { i, child ->
|
||||
if (!canSkipEncode(
|
||||
|
|
|
@ -44,6 +44,8 @@
|
|||
<!-- カスタマイズしない画面のImageButtonの図柄のtint -->
|
||||
<attr name="colorTextContent" format="color"/>
|
||||
|
||||
<attr name="colorTextHint" format="color"/>
|
||||
|
||||
<attr name="colorRefreshErrorBg" format="color"/>
|
||||
|
||||
<!-- プロフ背景を薄くするマスク #C0FFFFFF -->
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
|
||||
<!-- AppCompat ここまで ============================================ -->
|
||||
|
||||
<item name="colorTextHelp">@color/Light_colorTextHelp</item>
|
||||
<item name="colorLink">@color/Light_colorLink</item>
|
||||
<item name="list_item_bg_pressed_dragged">@color/Light_colotListItemDrag</item>
|
||||
<item name="color_column_header">@color/Light_colorColumnHeaderBg</item>
|
||||
|
@ -54,6 +53,9 @@
|
|||
<item name="colorColumnListItemText">@color/Light_colorTextColumnListItem</item>
|
||||
<item name="colorTimeSmall">@color/Light_colorTextTimeSmall</item>
|
||||
<item name="colorTextContent">@color/Light_colorTextContent</item>
|
||||
<item name="colorTextHint">@color/Light_colorTextHint</item>
|
||||
<item name="colorTextHelp">@color/Light_colorTextHelp</item>
|
||||
|
||||
<item name="colorProfileBackgroundMask">@color/Light_colorProfileBackgroundMask</item>
|
||||
<item name="colorShowMediaBackground">@color/Light_colorShowMediaBackground</item>
|
||||
<item name="colorShowMediaText">@color/Light_colorShowMediaText</item>
|
||||
|
@ -131,8 +133,6 @@
|
|||
|
||||
<!-- AppCompat ここまで ============================================ -->
|
||||
|
||||
<item name="colorTextHelp">@color/Dark_colorTextHelp</item>
|
||||
|
||||
<item name="colorLink">@color/Dark_colorLink</item>
|
||||
<item name="list_item_bg_pressed_dragged">@color/Dark_colorListItemDrag</item>
|
||||
<item name="color_column_header">@color/Dark_colorColumnHeader</item>
|
||||
|
@ -151,6 +151,8 @@
|
|||
<item name="colorColumnListItemText">@color/Dark_colorTextColumnListItem</item>
|
||||
<item name="colorTimeSmall">@color/Dark_colorTextTimeSmall</item>
|
||||
<item name="colorTextContent">@color/Dark_colorTextContent</item>
|
||||
<item name="colorTextHint">@color/Dark_colorTextHint</item>
|
||||
<item name="colorTextHelp">@color/Dark_colorTextHelp</item>
|
||||
|
||||
<item name="colorProfileBackgroundMask">@color/Dark_colorProfileBackgroundMask</item>
|
||||
<item name="colorShowMediaBackground">@color/Dark_colorShowMediaBackground</item>
|
||||
|
@ -233,7 +235,6 @@
|
|||
|
||||
<!-- AppCompat ここまで ============================================ -->
|
||||
|
||||
<item name="colorTextHelp">@color/Mastodon_colorTextHelp</item>
|
||||
|
||||
<item name="colorLink">@color/Mastodon_colorLink</item>
|
||||
<item name="list_item_bg_pressed_dragged">@color/Mastodon_colorListItemDrag</item>
|
||||
|
@ -253,6 +254,8 @@
|
|||
<item name="colorColumnListItemText">@color/Mastodon_colorTextColumnListItem</item>
|
||||
<item name="colorTimeSmall">@color/Mastodon_colorTextTimeSmall</item>
|
||||
<item name="colorTextContent">@color/Mastodon_colorTextContent</item>
|
||||
<item name="colorTextHint">@color/Mastodon_colorTextHint</item>
|
||||
<item name="colorTextHelp">@color/Mastodon_colorTextHelp</item>
|
||||
|
||||
<item name="colorProfileBackgroundMask">@color/Mastodon_colorProfileBackgroundMask</item>
|
||||
<item name="colorShowMediaBackground">@color/Mastodon_colorShowMediaBackground</item>
|
||||
|
|
Loading…
Reference in New Issue