-
-
Notifications
You must be signed in to change notification settings - Fork 96
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented figure and image Actually renders linear content WIP Liking this Great previews Layouts look good but html parsing is doing something wrong Not sure why some text is small but pretty good. Images could be larger in tables Fixed some bugs Fixed row colors for table Fixed so empty lists aren't added Added support for ArsTechnica stupid image styles No longer assumes full screen for dimensions Fixed empty space at end of blockquotes Fixed nullability assumptinos Added spans to table Added better handling of ending whitespace Added support for <span style> for bold/italic Fixed test Build apk for branch Fixed incorrect parsing of nested tables Optimized out single row tables Optimized single col tables
- Loading branch information
1 parent
0402559
commit e1f3c77
Showing
21 changed files
with
4,394 additions
and
2,035 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ name: Signed APKs | |
on: | ||
push: | ||
branches: | ||
- upgrades | ||
- table-layout | ||
|
||
jobs: | ||
signed_apk: | ||
|
889 changes: 889 additions & 0 deletions
889
app/src/main/java/com/nononsenseapps/feeder/model/html/HtmlLinearizer.kt
Large diffs are not rendered by default.
Oops, something went wrong.
293 changes: 293 additions & 0 deletions
293
app/src/main/java/com/nononsenseapps/feeder/model/html/LinearStuff.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,293 @@ | ||
package com.nononsenseapps.feeder.model.html | ||
|
||
import androidx.collection.ArrayMap | ||
|
||
data class LinearArticle( | ||
val elements: List<LinearElement>, | ||
) | ||
|
||
/** | ||
* A linear element can contain other linear elements | ||
*/ | ||
sealed interface LinearElement | ||
|
||
/** | ||
* Represents a list of items, ordered or unordered | ||
*/ | ||
data class LinearList( | ||
val ordered: Boolean, | ||
val items: List<LinearListItem>, | ||
) : LinearElement { | ||
fun isEmpty(): Boolean { | ||
return items.isEmpty() | ||
} | ||
|
||
fun isNotEmpty(): Boolean { | ||
return items.isNotEmpty() | ||
} | ||
|
||
class Builder(private val ordered: Boolean) { | ||
private val items: MutableList<LinearListItem> = mutableListOf() | ||
|
||
fun add(item: LinearListItem) { | ||
items.add(item) | ||
} | ||
|
||
fun build(): LinearList { | ||
return LinearList(ordered, items) | ||
} | ||
} | ||
|
||
companion object { | ||
fun build( | ||
ordered: Boolean, | ||
block: Builder.() -> Unit, | ||
): LinearList { | ||
return Builder(ordered).apply(block).build() | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Represents a single item in a list | ||
*/ | ||
data class LinearListItem( | ||
val content: List<LinearElement>, | ||
) { | ||
constructor(block: ListBuilderScope<LinearElement>.() -> Unit) : this(content = ListBuilderScope(block).items) | ||
|
||
constructor(vararg elements: LinearElement) : this(content = elements.toList()) | ||
|
||
fun isEmpty(): Boolean { | ||
return content.isEmpty() | ||
} | ||
|
||
fun isNotEmpty(): Boolean { | ||
return content.isNotEmpty() | ||
} | ||
|
||
class Builder { | ||
private val content: MutableList<LinearElement> = mutableListOf() | ||
|
||
fun add(element: LinearElement) { | ||
content.add(element) | ||
} | ||
|
||
fun build(): LinearListItem { | ||
return LinearListItem(content) | ||
} | ||
} | ||
|
||
companion object { | ||
fun build(block: Builder.() -> Unit): LinearListItem { | ||
return Builder().apply(block).build() | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Represents a table | ||
*/ | ||
data class LinearTable( | ||
val rowCount: Int, | ||
val colCount: Int, | ||
private val cellsReal: ArrayMap<Coordinate, LinearTableCellItem>, | ||
) : LinearElement { | ||
val cells: Map<Coordinate, LinearTableCellItem> | ||
get() = cellsReal | ||
|
||
constructor( | ||
rowCount: Int, | ||
colCount: Int, | ||
cells: List<LinearTableCellItem>, | ||
) : this( | ||
rowCount, | ||
colCount, | ||
ArrayMap<Coordinate, LinearTableCellItem>().apply { | ||
cells.forEachIndexed { index, item -> | ||
put(Coordinate(row = index / colCount, col = index % colCount), item) | ||
} | ||
}, | ||
) | ||
|
||
fun cellAt( | ||
row: Int, | ||
col: Int, | ||
): LinearTableCellItem? { | ||
return cells[Coordinate(row = row, col = col)] | ||
} | ||
|
||
class Builder { | ||
private val cells: ArrayMap<Coordinate, LinearTableCellItem> = ArrayMap() | ||
private var rowCount: Int = 0 | ||
private var colCount: Int = 0 | ||
private var currentRowColCount = 0 | ||
private var currentRow = 0 | ||
|
||
fun add(element: LinearTableCellItem) { | ||
check(rowCount > 0) { "Must add a row before adding cells" } | ||
|
||
// First find the first empty cell in this row | ||
var cellCoord = Coordinate(row = currentRow, col = currentRowColCount) | ||
while (cells[cellCoord] != null) { | ||
currentRowColCount++ | ||
cellCoord = cellCoord.copy(col = currentRowColCount) | ||
} | ||
|
||
currentRowColCount += element.colSpan | ||
if (currentRowColCount > colCount) { | ||
colCount = currentRowColCount | ||
} | ||
|
||
cells[cellCoord] = element | ||
|
||
// Insert filler elements for spanned cells | ||
for (r in 0 until element.rowSpan) { | ||
for (c in 0 until element.colSpan) { | ||
// Skip first since this is the cell itself | ||
if (r == 0 && c == 0) { | ||
continue | ||
} | ||
|
||
val fillerCoord = Coordinate(row = currentRow + r, col = currentRowColCount - element.colSpan + c) | ||
check(cells[fillerCoord] == null) { "Cell at filler $fillerCoord already exists" } | ||
cells[fillerCoord] = LinearTableCellItem.filler | ||
} | ||
} | ||
} | ||
|
||
fun newRow() { | ||
if (rowCount > 0) { | ||
currentRow++ | ||
} | ||
rowCount++ | ||
currentRowColCount = 0 | ||
} | ||
|
||
fun build(): LinearTable { | ||
return LinearTable(rowCount, colCount, cells) | ||
} | ||
} | ||
|
||
companion object { | ||
fun build(block: Builder.() -> Unit): LinearTable { | ||
return Builder().apply(block).build() | ||
} | ||
} | ||
} | ||
|
||
data class Coordinate( | ||
val row: Int, | ||
val col: Int, | ||
) | ||
|
||
/** | ||
* Represents a single cell in a table | ||
*/ | ||
data class LinearTableCellItem( | ||
val type: LinearTableCellItemType, | ||
val colSpan: Int, | ||
val rowSpan: Int, | ||
val content: List<LinearElement>, | ||
) { | ||
constructor( | ||
colSpan: Int, | ||
rowSpan: Int, | ||
type: LinearTableCellItemType, | ||
block: ListBuilderScope<LinearElement>.() -> Unit, | ||
) : this(colSpan = colSpan, rowSpan = rowSpan, type = type, content = ListBuilderScope(block).items) | ||
|
||
val isFiller | ||
get() = colSpan == filler.colSpan && rowSpan == filler.rowSpan | ||
|
||
class Builder( | ||
private val colSpan: Int, | ||
private val rowSpan: Int, | ||
private val type: LinearTableCellItemType, | ||
) { | ||
private val content: MutableList<LinearElement> = mutableListOf() | ||
|
||
fun add(element: LinearElement) { | ||
content.add(element) | ||
} | ||
|
||
fun build(): LinearTableCellItem { | ||
return LinearTableCellItem(colSpan = colSpan, rowSpan = rowSpan, type = type, content = content) | ||
} | ||
} | ||
|
||
companion object { | ||
fun build( | ||
colSpan: Int, | ||
rowSpan: Int, | ||
type: LinearTableCellItemType, | ||
block: Builder.() -> Unit, | ||
): LinearTableCellItem { | ||
return Builder(colSpan = colSpan, rowSpan = rowSpan, type = type).apply(block).build() | ||
} | ||
|
||
val filler = | ||
LinearTableCellItem( | ||
type = LinearTableCellItemType.DATA, | ||
colSpan = -1, | ||
rowSpan = -1, | ||
content = emptyList(), | ||
) | ||
} | ||
} | ||
|
||
enum class LinearTableCellItemType { | ||
HEADER, | ||
DATA, | ||
} | ||
|
||
data class LinearBlockQuote( | ||
val cite: String?, | ||
val content: List<LinearElement>, | ||
) : LinearElement { | ||
constructor(cite: String?, block: ListBuilderScope<LinearElement>.() -> Unit) : this(cite = cite, content = ListBuilderScope(block).items) | ||
|
||
constructor(cite: String?, vararg elements: LinearElement) : this(cite = cite, content = elements.toList()) | ||
} | ||
|
||
/** | ||
* Primitives can not contain other elements | ||
*/ | ||
sealed interface LinearPrimitive : LinearElement | ||
|
||
/** | ||
* Represents a text element. For example a paragraph, or a header. | ||
*/ | ||
data class LinearText( | ||
val text: String, | ||
val annotations: List<LinearTextAnnotation>, | ||
val blockStyle: LinearTextBlockStyle, | ||
) : LinearPrimitive { | ||
constructor(text: String, blockStyle: LinearTextBlockStyle, vararg annotations: LinearTextAnnotation) : this(text = text, blockStyle = blockStyle, annotations = annotations.toList()) | ||
} | ||
|
||
enum class LinearTextBlockStyle { | ||
TEXT, | ||
PRE_FORMATTED, | ||
CODE_BLOCK, | ||
} | ||
|
||
val LinearTextBlockStyle.shouldSoftWrap: Boolean | ||
get() = this == LinearTextBlockStyle.TEXT | ||
|
||
/** | ||
* Represents an image element | ||
*/ | ||
data class LinearImage( | ||
val candidates: List<LinearImageCandidate>, | ||
val caption: LinearText?, | ||
val link: String?, | ||
) : LinearElement | ||
|
||
data class LinearImageCandidate( | ||
val imgUri: String, | ||
val widthPx: Int?, | ||
val heightPx: Int?, | ||
val pixelDensity: Float?, | ||
val screenWidth: Int?, | ||
) |
54 changes: 54 additions & 0 deletions
54
app/src/main/java/com/nononsenseapps/feeder/model/html/LinearTextAnnotation.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package com.nononsenseapps.feeder.model.html | ||
|
||
data class LinearTextAnnotation( | ||
val data: LinearTextAnnotationData, | ||
/** | ||
* Inclusive start index | ||
*/ | ||
val start: Int, | ||
/** | ||
* Inclusive end index | ||
*/ | ||
var end: Int, | ||
) { | ||
val endExclusive | ||
get() = end + 1 | ||
} | ||
|
||
sealed interface LinearTextAnnotationData | ||
|
||
data object LinearTextAnnotationH1 : LinearTextAnnotationData | ||
|
||
data object LinearTextAnnotationH2 : LinearTextAnnotationData | ||
|
||
data object LinearTextAnnotationH3 : LinearTextAnnotationData | ||
|
||
data object LinearTextAnnotationH4 : LinearTextAnnotationData | ||
|
||
data object LinearTextAnnotationH5 : LinearTextAnnotationData | ||
|
||
data object LinearTextAnnotationH6 : LinearTextAnnotationData | ||
|
||
data object LinearTextAnnotationBold : LinearTextAnnotationData | ||
|
||
data object LinearTextAnnotationItalic : LinearTextAnnotationData | ||
|
||
data object LinearTextAnnotationMonospace : LinearTextAnnotationData | ||
|
||
data object LinearTextAnnotationUnderline : LinearTextAnnotationData | ||
|
||
data object LinearTextAnnotationStrikethrough : LinearTextAnnotationData | ||
|
||
data object LinearTextAnnotationSuperscript : LinearTextAnnotationData | ||
|
||
data object LinearTextAnnotationSubscript : LinearTextAnnotationData | ||
|
||
data class LinearTextAnnotationFont( | ||
val face: String, | ||
) : LinearTextAnnotationData | ||
|
||
data object LinearTextAnnotationCode : LinearTextAnnotationData | ||
|
||
data class LinearTextAnnotationLink( | ||
val href: String, | ||
) : LinearTextAnnotationData |
Oops, something went wrong.