From daa088eb429183a5f34799571f8a34364906c562 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Fri, 8 Jul 2022 14:01:48 +0200 Subject: [PATCH 01/15] Advanced styling of headings --- app/src/main/res/layout/activity_main.xml | 6 +- .../kotlin/org/wordpress/aztec/AztecText.kt | 56 ++++++--- .../aztec/formatting/BlockFormatter.kt | 106 +++++++++--------- .../wordpress/aztec/spans/AztecHeadingSpan.kt | 100 ++++++++++++----- .../wordpress/aztec/spans/ParagraphSpan.kt | 52 +++++++-- aztec/src/main/res/values/attrs.xml | 13 +++ aztec/src/main/res/values/dimens.xml | 1 + 7 files changed, 231 insertions(+), 103 deletions(-) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index fedc7ca55..825e8534b 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -39,7 +39,11 @@ android:scrollbars="vertical" android:imeOptions="flagNoExtractUi" aztec:historyEnable="true" - aztec:historySize="10"/> + aztec:historySize="10" + aztec:headingOneFontSize="24sp" + aztec:headingOneFontColor="@color/blue_light" + aztec:headingThreeFontSize="16sp" + aztec:headingThreeFontColor="#F09389"/> ) { + data class HeadingStyle(val fontSize: Int, val fontColor: Int) + } + data class ExclusiveBlockStyles(val enabled: Boolean = false, val verticalParagraphMargin: Int) + data class ParagraphStyle(val verticalMargin: Int) fun indent() { listFormatter.indentList() @@ -208,7 +211,7 @@ class BlockFormatter(editor: AztecText, var changed = false // try to remove block styling when pressing backspace at the beginning of the text - editableText.getSpans(0, 0, IAztecBlockSpan::class.java).forEach { + editableText.getSpans(0, 0, IAztecBlockSpan::class.java).forEach { it -> val spanEnd = editableText.getSpanEnd(it) val indexOfNewline = editableText.indexOf('\n').let { if (it != -1) it else editableText.length } @@ -318,7 +321,7 @@ class BlockFormatter(editor: AztecText, } fun removeBlockStyle(textFormat: ITextFormat) { - removeBlockStyle(textFormat, selectionStart, selectionEnd, makeBlock(textFormat, 0).map { it -> it.javaClass }) + removeBlockStyle(textFormat, selectionStart, selectionEnd, makeBlock(textFormat, 0).map { it.javaClass }) } fun removeEntireBlock(type: Class) { @@ -329,7 +332,7 @@ class BlockFormatter(editor: AztecText, } fun removeBlockStyle(textFormat: ITextFormat, originalStart: Int, originalEnd: Int, - spanTypes: List> = Arrays.asList(IAztecBlockSpan::class.java), + spanTypes: List> = listOf(IAztecBlockSpan::class.java), ignoreLineBounds: Boolean = false) { var start = originalStart var end = originalEnd @@ -341,8 +344,8 @@ class BlockFormatter(editor: AztecText, getBoundsOfText(editableText, start, end) } - var startOfBounds = boundsOfSelectedText.start - var endOfBounds = boundsOfSelectedText.endInclusive + var startOfBounds = boundsOfSelectedText.first + var endOfBounds = boundsOfSelectedText.last if (ignoreLineBounds) { val hasPrecedingSpans = spanTypes.any { spanType -> @@ -447,7 +450,7 @@ class BlockFormatter(editor: AztecText, AztecTextFormat.FORMAT_HEADING_5, AztecTextFormat.FORMAT_HEADING_6 -> listOf(createHeadingSpan(nestingLevel, textFormat, attrs, alignmentRendering, headerStyle)) AztecTextFormat.FORMAT_PREFORMAT -> listOf(createPreformatSpan(nestingLevel, alignmentRendering, attrs, preformatStyle)) - else -> listOf(createParagraphSpan(nestingLevel, alignmentRendering, attrs)) + else -> listOf(createParagraphSpan(nestingLevel, alignmentRendering, attrs, paragraphStyle)) } } @@ -476,7 +479,7 @@ class BlockFormatter(editor: AztecText, AztecTextFormat.FORMAT_HEADING_5, AztecTextFormat.FORMAT_HEADING_6 -> makeBlockSpan(AztecHeadingSpan::class, textFormat, nestingLevel, attrs) AztecTextFormat.FORMAT_PREFORMAT -> makeBlockSpan(AztecPreformatSpan::class, textFormat, nestingLevel, attrs) - else -> createParagraphSpan(nestingLevel, alignmentRendering, attrs) + else -> createParagraphSpan(nestingLevel, alignmentRendering, attrs, paragraphStyle) } } @@ -490,7 +493,7 @@ class BlockFormatter(editor: AztecText, typeIsAssignableTo(AztecQuoteSpan::class) -> createAztecQuoteSpan(nestingLevel, attrs, alignmentRendering, quoteStyle) typeIsAssignableTo(AztecHeadingSpan::class) -> createHeadingSpan(nestingLevel, textFormat, attrs, alignmentRendering, headerStyle) typeIsAssignableTo(AztecPreformatSpan::class) -> createPreformatSpan(nestingLevel, alignmentRendering, attrs, preformatStyle) - else -> createParagraphSpan(nestingLevel, alignmentRendering, attrs) + else -> createParagraphSpan(nestingLevel, alignmentRendering, attrs, paragraphStyle) } } @@ -500,6 +503,7 @@ class BlockFormatter(editor: AztecText, is AztecUnorderedListSpan -> blockElement.listStyle = listStyle is AztecTaskListSpan -> blockElement.listStyle = listStyle is AztecQuoteSpan -> blockElement.quoteStyle = quoteStyle + is ParagraphSpan -> blockElement.paragraphStyle = paragraphStyle is AztecPreformatSpan -> blockElement.preformatStyle = preformatStyle is AztecHeadingSpan -> blockElement.headerStyle = headerStyle } @@ -603,10 +607,10 @@ class BlockFormatter(editor: AztecText, } else if (selectionStartIsOnTheNewLine) { val isSingleCharacterLine = (selectionStart > 1 && editableText[selectionStart - 1] != '\n' && editableText[selectionStart - 2] == '\n') || selectionStart == 1 - if (isSingleCharacterLine) { - indexOfFirstLineBreak = selectionStart - 1 + indexOfFirstLineBreak = if (isSingleCharacterLine) { + selectionStart - 1 } else { - indexOfFirstLineBreak = editable.lastIndexOf("\n", selectionStart - 1) + 1 + editable.lastIndexOf("\n", selectionStart - 1) + 1 } if (isTrailingNewlineAtTheEndOfSelection) { indexOfLastLineBreak = editable.indexOf("\n", selectionEnd - 1) @@ -636,12 +640,12 @@ class BlockFormatter(editor: AztecText, } val boundsOfSelectedText = getBoundsOfText(editableText, start, end) - var spans = getAlignedSpans(null, boundsOfSelectedText.start, boundsOfSelectedText.endInclusive) + var spans = getAlignedSpans(null, boundsOfSelectedText.first, boundsOfSelectedText.last) if (start == end) { - if (start == boundsOfSelectedText.start && spans.size > 1) { + if (start == boundsOfSelectedText.first && spans.size > 1) { spans = spans.filter { editableText.getSpanEnd(it) != start } - } else if (start == boundsOfSelectedText.endInclusive && spans.size > 1) { + } else if (start == boundsOfSelectedText.last && spans.size > 1) { spans = spans.filter { editableText.getSpanStart(it) != start } } } @@ -649,12 +653,12 @@ class BlockFormatter(editor: AztecText, if (spans.isNotEmpty()) { spans.filter { it !is AztecListSpan }.forEach { changeAlignment(it, textFormat) } } else { - val nestingLevel = IAztecNestable.getNestingLevelAt(editableText, boundsOfSelectedText.start) + val nestingLevel = IAztecNestable.getNestingLevelAt(editableText, boundsOfSelectedText.first) val alignment = getAlignment(textFormat, - editableText.subSequence(boundsOfSelectedText.start until boundsOfSelectedText.endInclusive)) - editableText.setSpan(createParagraphSpan(nestingLevel, alignment), - boundsOfSelectedText.start, boundsOfSelectedText.endInclusive, Spanned.SPAN_PARAGRAPH) + editableText.subSequence(boundsOfSelectedText.first until boundsOfSelectedText.last)) + editableText.setSpan(createParagraphSpan(nestingLevel, alignment, paragraphStyle = paragraphStyle), + boundsOfSelectedText.first, boundsOfSelectedText.last, Spanned.SPAN_PARAGRAPH) } } @@ -678,9 +682,9 @@ class BlockFormatter(editor: AztecText, if (start != end) { // we want to push line blocks as deep as possible, because they can't contain other block elements (e.g. headings) if (spanToApply is IAztecLineBlockSpan) { - applyLineBlock(blockElementType, boundsOfSelectedText.start, boundsOfSelectedText.endInclusive) + applyLineBlock(blockElementType, boundsOfSelectedText.first, boundsOfSelectedText.last) } else { - val delimiters = getTopBlockDelimiters(boundsOfSelectedText.start, boundsOfSelectedText.endInclusive) + val delimiters = getTopBlockDelimiters(boundsOfSelectedText.first, boundsOfSelectedText.last) for (i in 0 until delimiters.size - 1) { pushNewBlock(delimiters[i], delimiters[i + 1], blockElementType) } @@ -688,12 +692,12 @@ class BlockFormatter(editor: AztecText, editor.setSelection(editor.selectionStart) } else { - val startOfLine = boundsOfSelectedText.start - val endOfLine = boundsOfSelectedText.endInclusive + val startOfLine = boundsOfSelectedText.first + val endOfLine = boundsOfSelectedText.last // we can't add blocks around partial block elements (i.e. list items), everything must go inside - val isWithinPartialBlock = editableText.getSpans(boundsOfSelectedText.start, - boundsOfSelectedText.endInclusive, IAztecCompositeBlockSpan::class.java) + val isWithinPartialBlock = editableText.getSpans(boundsOfSelectedText.first, + boundsOfSelectedText.last, IAztecCompositeBlockSpan::class.java) .any { it.nestingLevel == nestingLevel - 1 } val startOfBlock = mergeWithBlockAbove(startOfLine, endOfLine, spanToApply, nestingLevel, isWithinPartialBlock, blockElementType) @@ -808,7 +812,7 @@ class BlockFormatter(editor: AztecText, for (i in lines.indices) { val lineLength = lines[i].length - val lineStart = (0..i - 1).sumBy { lines[it].length + 1 } + val lineStart = (0 until i).sumBy { lines[it].length + 1 } val lineEnd = (lineStart + lineLength).let { if ((start + it) != editableText.length) it + 1 else it // include the newline or not @@ -829,7 +833,7 @@ class BlockFormatter(editor: AztecText, val splitLength = lines[i].length val lineStart = start + (0 until i).sumBy { lines[it].length + 1 } - val lineEnd = Math.min(lineStart + splitLength + 1, end) // +1 to include the newline + val lineEnd = (lineStart + splitLength + 1).coerceAtMost(end) // +1 to include the newline val lineLength = lineEnd - lineStart if (lineLength == 0) continue @@ -846,7 +850,7 @@ class BlockFormatter(editor: AztecText, val splitLength = lines[i].length val lineStart = start + (0 until i).sumBy { lines[it].length + 1 } - val lineEnd = Math.min(lineStart + splitLength + 1, end) // +1 to include the newline + val lineEnd = (lineStart + splitLength + 1).coerceAtMost(end) // +1 to include the newline val lineLength = lineEnd - lineStart if (lineLength == 0) continue @@ -872,7 +876,7 @@ class BlockFormatter(editor: AztecText, } private fun liftListBlock(listSpan: Class, start: Int, end: Int) { - editableText.getSpans(start, end, listSpan).forEach { + editableText.getSpans(start, end, listSpan).forEach { it -> val wrapper = SpanWrapper(editableText, it) editableText.getSpans(wrapper.start, wrapper.end, AztecListItemSpan::class.java).forEach { editableText.removeSpan(it) } @@ -968,7 +972,7 @@ class BlockFormatter(editor: AztecText, val list = ArrayList() for (i in lines.indices) { - val lineStart = (0..i - 1).sumBy { lines[it].length + 1 } + val lineStart = (0 until i).sumBy { lines[it].length + 1 } val lineEnd = lineStart + lines[i].length if (lineStart >= lineEnd) { @@ -984,8 +988,8 @@ class BlockFormatter(editor: AztecText, * multiple lines (before), current partially or entirely selected */ if ((lineStart >= selStart && selEnd >= lineEnd) - || (lineStart <= selEnd && selEnd <= lineEnd) - || (lineStart <= selStart && selStart <= lineEnd)) { + || (selEnd in lineStart..lineEnd) + || (selStart in lineStart..lineEnd)) { list.add(i) } } @@ -1002,7 +1006,7 @@ class BlockFormatter(editor: AztecText, return false } - val start = (0..index - 1).sumBy { lines[it].length + 1 } + val start = (0 until index).sumBy { lines[it].length + 1 } val end = start + lines[index].length if (start >= end) { @@ -1012,20 +1016,20 @@ class BlockFormatter(editor: AztecText, val spans = editableText.getSpans(start, end, AztecHeadingSpan::class.java) for (span in spans) { - when (textFormat) { + return when (textFormat) { AztecTextFormat.FORMAT_HEADING_1 -> - return span.heading == AztecHeadingSpan.Heading.H1 + span.heading == AztecHeadingSpan.Heading.H1 AztecTextFormat.FORMAT_HEADING_2 -> - return span.heading == AztecHeadingSpan.Heading.H2 + span.heading == AztecHeadingSpan.Heading.H2 AztecTextFormat.FORMAT_HEADING_3 -> - return span.heading == AztecHeadingSpan.Heading.H3 + span.heading == AztecHeadingSpan.Heading.H3 AztecTextFormat.FORMAT_HEADING_4 -> - return span.heading == AztecHeadingSpan.Heading.H4 + span.heading == AztecHeadingSpan.Heading.H4 AztecTextFormat.FORMAT_HEADING_5 -> - return span.heading == AztecHeadingSpan.Heading.H5 + span.heading == AztecHeadingSpan.Heading.H5 AztecTextFormat.FORMAT_HEADING_6 -> - return span.heading == AztecHeadingSpan.Heading.H6 - else -> return false + span.heading == AztecHeadingSpan.Heading.H6 + else -> false } } @@ -1098,7 +1102,7 @@ class BlockFormatter(editor: AztecText, val list = ArrayList() for (i in lines.indices) { - val lineStart = (0..i - 1).sumBy { lines[it].length + 1 } + val lineStart = (0 until i).sumBy { lines[it].length + 1 } val lineEnd = lineStart + lines[i].length if (lineStart >= lineEnd) { @@ -1114,8 +1118,8 @@ class BlockFormatter(editor: AztecText, * multiple lines (before), current partially or entirely selected */ if ((lineStart >= selStart && selEnd >= lineEnd) - || (lineStart <= selEnd && selEnd <= lineEnd) - || (lineStart <= selStart && selStart <= lineEnd)) { + || (selEnd in lineStart..lineEnd) + || (selStart in lineStart..lineEnd)) { list.add(i) } } @@ -1131,7 +1135,7 @@ class BlockFormatter(editor: AztecText, return false } - val start = (0..index - 1).sumBy { lines[it].length + 1 } + val start = (0 until index).sumBy { lines[it].length + 1 } val end = start + lines[index].length if (start >= end) { @@ -1209,7 +1213,7 @@ class BlockFormatter(editor: AztecText, val spanStart = editableText.getSpanStart(heading) val spanEnd = editableText.getSpanEnd(heading) val spanFlags = editableText.getSpanFlags(heading) - val spanType = makeBlock(heading.textFormat, 0).map { it -> it.javaClass } + val spanType = makeBlock(heading.textFormat, 0).map { it.javaClass } removeBlockStyle(heading.textFormat, spanStart, spanEnd, spanType) editableText.setSpan(AztecPreformatSpan(heading.nestingLevel, heading.attributes, preformatStyle), spanStart, spanEnd, spanFlags) @@ -1229,7 +1233,7 @@ class BlockFormatter(editor: AztecText, val spanStart = editableText.getSpanStart(preformat) val spanEnd = editableText.getSpanEnd(preformat) val spanFlags = editableText.getSpanFlags(preformat) - val spanType = makeBlock(AztecTextFormat.FORMAT_PREFORMAT, 0).map { it -> it.javaClass } + val spanType = makeBlock(AztecTextFormat.FORMAT_PREFORMAT, 0).map { it.javaClass } removeBlockStyle(AztecTextFormat.FORMAT_PREFORMAT, spanStart, spanEnd, spanType) val headingSpan = createHeadingSpan( diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecHeadingSpan.kt b/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecHeadingSpan.kt index 8be42f052..dc5cbd529 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecHeadingSpan.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecHeadingSpan.kt @@ -18,8 +18,8 @@ fun createHeadingSpan(nestingLevel: Int, tag: String, attributes: AztecAttributes, alignmentRendering: AlignmentRendering, - headerStyle: BlockFormatter.HeaderStyle = BlockFormatter.HeaderStyle(0) -) : AztecHeadingSpan { + headerStyle: BlockFormatter.HeaderStyles = BlockFormatter.HeaderStyles(0, emptyMap()) +): AztecHeadingSpan { val textFormat = when (tag.toLowerCase(Locale.getDefault())) { "h1" -> AztecTextFormat.FORMAT_HEADING_1 "h2" -> AztecTextFormat.FORMAT_HEADING_2 @@ -36,8 +36,8 @@ fun createHeadingSpan(nestingLevel: Int, textFormat: ITextFormat, attributes: AztecAttributes, alignmentRendering: AlignmentRendering, - headerStyle: BlockFormatter.HeaderStyle = BlockFormatter.HeaderStyle(0) -) : AztecHeadingSpan = + headerStyle: BlockFormatter.HeaderStyles = BlockFormatter.HeaderStyles(0, emptyMap()) +): AztecHeadingSpan = when (alignmentRendering) { AlignmentRendering.SPAN_LEVEL -> AztecHeadingSpanAligned(nestingLevel, textFormat, attributes, headerStyle) AlignmentRendering.VIEW_LEVEL -> AztecHeadingSpan(nestingLevel, textFormat, attributes, headerStyle) @@ -55,7 +55,7 @@ class AztecHeadingSpanAligned( override var nestingLevel: Int, textFormat: ITextFormat, override var attributes: AztecAttributes, - override var headerStyle: BlockFormatter.HeaderStyle, + override var headerStyle: BlockFormatter.HeaderStyles, override var align: Layout.Alignment? = null ) : AztecHeadingSpan(nestingLevel, textFormat, attributes, headerStyle), IAztecAlignmentSpan @@ -63,7 +63,7 @@ open class AztecHeadingSpan( override var nestingLevel: Int, textFormat: ITextFormat, override var attributes: AztecAttributes, - open var headerStyle: BlockFormatter.HeaderStyle + open var headerStyle: BlockFormatter.HeaderStyles ) : MetricAffectingSpan(), IAztecLineBlockSpan, LineHeightSpan, UpdateLayout { override val TAG: String get() = heading.tag @@ -80,7 +80,7 @@ open class AztecHeadingSpan( lateinit var heading: Heading var previousFontMetrics: Paint.FontMetricsInt? = null - var previousTextScale: Float = 1.0f + private var previousHeadingSize: HeadingSize = HeadingSize.Scale(1.0f) var previousSpacing: Float? = null enum class Heading constructor(internal val scale: Float, internal val tag: String) { @@ -93,22 +93,24 @@ open class AztecHeadingSpan( } companion object { - private val SCALE_H1: Float = 1.73f - private val SCALE_H2: Float = 1.32f - private val SCALE_H3: Float = 1.02f - private val SCALE_H4: Float = 0.87f - private val SCALE_H5: Float = 0.72f - private val SCALE_H6: Float = 0.60f + private const val SCALE_H1: Float = 1.73f + private const val SCALE_H2: Float = 1.32f + private const val SCALE_H3: Float = 1.02f + private const val SCALE_H4: Float = 0.87f + private const val SCALE_H5: Float = 0.72f + private const val SCALE_H6: Float = 0.60f fun textFormatToHeading(textFormat: ITextFormat): Heading { - when (textFormat) { - AztecTextFormat.FORMAT_HEADING_1 -> return AztecHeadingSpan.Heading.H1 - AztecTextFormat.FORMAT_HEADING_2 -> return AztecHeadingSpan.Heading.H2 - AztecTextFormat.FORMAT_HEADING_3 -> return AztecHeadingSpan.Heading.H3 - AztecTextFormat.FORMAT_HEADING_4 -> return AztecHeadingSpan.Heading.H4 - AztecTextFormat.FORMAT_HEADING_5 -> return AztecHeadingSpan.Heading.H5 - AztecTextFormat.FORMAT_HEADING_6 -> return AztecHeadingSpan.Heading.H6 - else -> { return AztecHeadingSpan.Heading.H1 } + return when (textFormat) { + AztecTextFormat.FORMAT_HEADING_1 -> Heading.H1 + AztecTextFormat.FORMAT_HEADING_2 -> Heading.H2 + AztecTextFormat.FORMAT_HEADING_3 -> Heading.H3 + AztecTextFormat.FORMAT_HEADING_4 -> Heading.H4 + AztecTextFormat.FORMAT_HEADING_5 -> Heading.H5 + AztecTextFormat.FORMAT_HEADING_6 -> Heading.H6 + else -> { + Heading.H1 + } } } } @@ -134,14 +136,16 @@ open class AztecHeadingSpan( var addedTopPadding = false var addedBottomPadding = false + val verticalPadding = headerStyle.verticalPadding + if (start == spanStart || start < spanStart) { - fm.ascent -= headerStyle.verticalPadding - fm.top -= headerStyle.verticalPadding + fm.ascent -= verticalPadding + fm.top -= verticalPadding addedTopPadding = true } if (end == spanEnd || spanEnd < end) { - fm.descent += headerStyle.verticalPadding - fm.bottom += headerStyle.verticalPadding + fm.descent += verticalPadding + fm.bottom += verticalPadding addedBottomPadding = true } @@ -158,19 +162,53 @@ open class AztecHeadingSpan( } override fun updateDrawState(textPaint: TextPaint) { - textPaint.textSize *= heading.scale + when (val headingSize = getHeadingSize()) { + is HeadingSize.Scale -> { + textPaint.textSize *= heading.scale + } + is HeadingSize.Size -> { + textPaint.textSize = headingSize.value.toFloat() + } + } textPaint.isFakeBoldText = true + getHeadingColor()?.let { + textPaint.color = it + } } - override fun updateMeasureState(textPaint: TextPaint) { + override fun updateMeasureState(paint: TextPaint) { + val headingSize = getHeadingSize() // when font size changes - reset cached font metrics to reapply vertical padding - if (previousTextScale != heading.scale || previousSpacing != textPaint.fontSpacing) { + if (headingSize != previousHeadingSize || previousSpacing != paint.fontSpacing) { previousFontMetrics = null } - previousTextScale = heading.scale - previousSpacing = textPaint.fontSpacing + previousHeadingSize = headingSize + previousSpacing = paint.fontSpacing + when (headingSize) { + is HeadingSize.Scale -> { + paint.textSize *= heading.scale + } + is HeadingSize.Size -> { + paint.textSize = headingSize.value.toFloat() + } + } + getHeadingColor()?.let { + paint.color = it + } + } + + private fun getHeadingSize(): HeadingSize { + return headerStyle.styles[heading]?.fontSize?.takeIf { it > 0 }?.let { HeadingSize.Size(it) } + ?: HeadingSize.Scale(heading.scale) + } + + private fun getHeadingColor(): Int? { + return headerStyle.styles[heading]?.fontColor?.takeIf { it != 0 } + } - textPaint.textSize *= heading.scale + sealed class HeadingSize { + data class Scale(val value: Float): HeadingSize() + data class Size(val value: Int): HeadingSize() } override fun toString() = "AztecHeadingSpan : $TAG" diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/spans/ParagraphSpan.kt b/aztec/src/main/kotlin/org/wordpress/aztec/spans/ParagraphSpan.kt index 729987a34..8f22ae74f 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/spans/ParagraphSpan.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/spans/ParagraphSpan.kt @@ -1,23 +1,29 @@ package org.wordpress.aztec.spans +import android.graphics.Paint import android.text.Layout +import android.text.Spanned +import android.text.style.LineHeightSpan import org.wordpress.aztec.AlignmentRendering import org.wordpress.aztec.AztecAttributes import org.wordpress.aztec.AztecTextFormat import org.wordpress.aztec.ITextFormat +import org.wordpress.aztec.formatting.BlockFormatter fun createParagraphSpan(nestingLevel: Int, alignmentRendering: AlignmentRendering, - attributes: AztecAttributes = AztecAttributes()): IAztecBlockSpan = + attributes: AztecAttributes = AztecAttributes(), + paragraphStyle: BlockFormatter.ParagraphStyle = BlockFormatter.ParagraphStyle(0)): IAztecBlockSpan = when (alignmentRendering) { - AlignmentRendering.SPAN_LEVEL -> ParagraphSpanAligned(nestingLevel, attributes, null) - AlignmentRendering.VIEW_LEVEL -> ParagraphSpan(nestingLevel, attributes) + AlignmentRendering.SPAN_LEVEL -> ParagraphSpanAligned(nestingLevel, attributes, null, paragraphStyle) + AlignmentRendering.VIEW_LEVEL -> ParagraphSpan(nestingLevel, attributes, paragraphStyle) } fun createParagraphSpan(nestingLevel: Int, align: Layout.Alignment?, - attributes: AztecAttributes = AztecAttributes()): IAztecBlockSpan = - ParagraphSpanAligned(nestingLevel, attributes, align) + attributes: AztecAttributes = AztecAttributes(), + paragraphStyle: BlockFormatter.ParagraphStyle = BlockFormatter.ParagraphStyle(0)): IAztecBlockSpan = + ParagraphSpanAligned(nestingLevel, attributes, align, paragraphStyle) /** * We need to have two classes for handling alignment at either the Span-level (ParagraphSpanAligned) @@ -29,7 +35,38 @@ fun createParagraphSpan(nestingLevel: Int, */ open class ParagraphSpan( override var nestingLevel: Int, - override var attributes: AztecAttributes) : IAztecBlockSpan { + override var attributes: AztecAttributes, + var paragraphStyle: BlockFormatter.ParagraphStyle = BlockFormatter.ParagraphStyle(0)) + : IAztecBlockSpan, LineHeightSpan { + + private var removeTopPadding = false + + override fun chooseHeight(text: CharSequence, start: Int, end: Int, spanstartv: Int, lineHeight: Int, fm: Paint.FontMetricsInt) { + val spanned = text as Spanned + val spanStart = spanned.getSpanStart(this) + val spanEnd = spanned.getSpanEnd(this) + val isFirstLine = start <= spanStart + val isLastLine = spanEnd <= end + if (isFirstLine) { + removeTopPadding = true + fm.ascent -= paragraphStyle.verticalMargin + fm.top -= paragraphStyle.verticalMargin + } + if (isLastLine) { + fm.descent += paragraphStyle.verticalMargin + fm.bottom += paragraphStyle.verticalMargin + removeTopPadding = false + } + if (!isFirstLine && !isLastLine && removeTopPadding) { + removeTopPadding = false + if (fm.ascent + paragraphStyle.verticalMargin < 0) { + fm.ascent += paragraphStyle.verticalMargin + } + if (fm.top + paragraphStyle.verticalMargin < 0) { + fm.top += paragraphStyle.verticalMargin + } + } + } override var TAG: String = "p" @@ -41,4 +78,5 @@ open class ParagraphSpan( class ParagraphSpanAligned( nestingLevel: Int, attributes: AztecAttributes, - override var align: Layout.Alignment?) : ParagraphSpan(nestingLevel, attributes), IAztecAlignmentSpan + override var align: Layout.Alignment?, + paragraphStyle: BlockFormatter.ParagraphStyle) : ParagraphSpan(nestingLevel, attributes, paragraphStyle), IAztecAlignmentSpan diff --git a/aztec/src/main/res/values/attrs.xml b/aztec/src/main/res/values/attrs.xml index ba266b226..e1c1fec29 100644 --- a/aztec/src/main/res/values/attrs.xml +++ b/aztec/src/main/res/values/attrs.xml @@ -5,6 +5,7 @@ + @@ -35,6 +36,18 @@ + + + + + + + + + + + + diff --git a/aztec/src/main/res/values/dimens.xml b/aztec/src/main/res/values/dimens.xml index 410d28cf9..0b0af0646 100644 --- a/aztec/src/main/res/values/dimens.xml +++ b/aztec/src/main/res/values/dimens.xml @@ -21,6 +21,7 @@ 16dp 2dp 8dp + 8dp 8dp 0dp 1.0 From 114fe93bb6f1adce8a4bfc9fd4ccffafd9b49c14 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Fri, 8 Jul 2022 14:05:14 +0200 Subject: [PATCH 02/15] Fix lint issues --- aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt | 2 +- .../main/kotlin/org/wordpress/aztec/spans/AztecHeadingSpan.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt b/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt index 421eacc63..a9d025020 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt @@ -452,7 +452,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown styles.getDimensionPixelSize(R.styleable.AztecText_quotePadding, 0), styles.getDimensionPixelSize(R.styleable.AztecText_quoteWidth, 0), verticalParagraphPadding), - headerStyle = BlockFormatter.HeaderStyles(verticalHeadingMargin,mapOf( + headerStyle = BlockFormatter.HeaderStyles(verticalHeadingMargin, mapOf( AztecHeadingSpan.Heading.H1 to BlockFormatter.HeaderStyles.HeadingStyle( styles.getDimensionPixelSize(R.styleable.AztecText_headingOneFontSize, 0), styles.getColor(R.styleable.AztecText_headingOneFontColor, 0) diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecHeadingSpan.kt b/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecHeadingSpan.kt index dc5cbd529..7d64d6dea 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecHeadingSpan.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/spans/AztecHeadingSpan.kt @@ -207,8 +207,8 @@ open class AztecHeadingSpan( } sealed class HeadingSize { - data class Scale(val value: Float): HeadingSize() - data class Size(val value: Int): HeadingSize() + data class Scale(val value: Float) : HeadingSize() + data class Size(val value: Int) : HeadingSize() } override fun toString() = "AztecHeadingSpan : $TAG" From d43e5292e25cd2fda8a9de12cfb44e9508240288 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Fri, 8 Jul 2022 15:05:26 +0200 Subject: [PATCH 03/15] Remove bottom margin from placeholder --- .../java/org/wordpress/aztec/placeholders/PlaceholderManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt b/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt index 84aa22739..bdaa10cf7 100644 --- a/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt +++ b/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt @@ -168,7 +168,7 @@ class PlaceholderManager( parentTextViewRect.bottom - parentTextViewRect.top - 20 ) val padding = 10 - params.setMargins(parentTextViewRect.left + padding, parentTextViewRect.top + padding, parentTextViewRect.right - padding, parentTextViewRect.bottom - padding) + params.setMargins(parentTextViewRect.left + padding, parentTextViewRect.top + padding, parentTextViewRect.right - padding, 0) box.layoutParams = params box.tag = uuid box.setBackgroundColor(Color.TRANSPARENT) From 85b8704a0cb4dfe8aebc49ac24c182bccaf94830 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Fri, 8 Jul 2022 16:09:46 +0200 Subject: [PATCH 04/15] Use aztecText padding in placeholder --- .../org/wordpress/aztec/placeholders/PlaceholderManager.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt b/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt index bdaa10cf7..96658c861 100644 --- a/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt +++ b/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt @@ -168,7 +168,12 @@ class PlaceholderManager( parentTextViewRect.bottom - parentTextViewRect.top - 20 ) val padding = 10 - params.setMargins(parentTextViewRect.left + padding, parentTextViewRect.top + padding, parentTextViewRect.right - padding, 0) + params.setMargins( + parentTextViewRect.left + padding + aztecText.paddingStart, + parentTextViewRect.top + padding, + parentTextViewRect.right - padding - aztecText.paddingEnd, + 0 + ) box.layoutParams = params box.tag = uuid box.setBackgroundColor(Color.TRANSPARENT) From f996062791ba7125069a208775e9a799ae73fd48 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Thu, 14 Jul 2022 14:34:15 +0200 Subject: [PATCH 05/15] Fix bug when cloning a heading --- .../main/kotlin/org/wordpress/aztec/handlers/HeadingHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/handlers/HeadingHandler.kt b/aztec/src/main/kotlin/org/wordpress/aztec/handlers/HeadingHandler.kt index c1515fff6..4ee9737ed 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/handlers/HeadingHandler.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/handlers/HeadingHandler.kt @@ -65,7 +65,7 @@ class HeadingHandler(private val alignmentRendering: AlignmentRendering) : Block companion object { fun cloneHeading(text: Spannable, block: AztecHeadingSpan, alignmentRendering: AlignmentRendering, start: Int, end: Int) { - set(text, createHeadingSpan(block.nestingLevel, block.textFormat, block.attributes, alignmentRendering), start, end) + set(text, createHeadingSpan(block.nestingLevel, block.textFormat, block.attributes, alignmentRendering, block.headerStyle), start, end) } } } From cf9f955092065aa1ad153423e0d1f8ba831af248 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Thu, 14 Jul 2022 15:28:57 +0200 Subject: [PATCH 06/15] Add limit to Proportion.Ratio on PlaceholderManager.kt --- .../aztec/placeholders/PlaceholderManager.kt | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt b/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt index 96658c861..8fab1af96 100644 --- a/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt +++ b/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt @@ -392,7 +392,12 @@ class PlaceholderManager( } else { height.ratio } - (ratio * calculateWidth(attrs, windowWidth)).toInt() + val result = (ratio * calculateWidth(attrs, windowWidth)).toInt() + if (height.limit != null && height.limit < result) { + height.limit + } else { + result + } } } } @@ -411,15 +416,20 @@ class PlaceholderManager( width.ratio > 1.0 -> 1.0f else -> width.ratio } - (safeRatio * windowWidth).toInt() + val result = (safeRatio * windowWidth).toInt() + if (width.limit != null && result > width.limit) { + width.limit + } else { + result + } } } } } sealed class Proportion { - data class Fixed(val value: Int) : Proportion() - data class Ratio(val ratio: Float) : Proportion() + data class Fixed(val value: Int, val limit: Int? = null) : Proportion() + data class Ratio(val ratio: Float, val limit: Int? = null) : Proportion() } } From 213b7edeeffeda1b6a4a9fafbbd0f20f6f073c05 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Fri, 15 Jul 2022 10:08:54 +0200 Subject: [PATCH 07/15] Remove changes to layout activity_main.xml --- app/src/main/res/layout/activity_main.xml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 825e8534b..fedc7ca55 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -39,11 +39,7 @@ android:scrollbars="vertical" android:imeOptions="flagNoExtractUi" aztec:historyEnable="true" - aztec:historySize="10" - aztec:headingOneFontSize="24sp" - aztec:headingOneFontColor="@color/blue_light" - aztec:headingThreeFontSize="16sp" - aztec:headingThreeFontColor="#F09389"/> + aztec:historySize="10"/> Date: Fri, 15 Jul 2022 11:31:01 +0200 Subject: [PATCH 08/15] Add and remove new line in order to redraw a heading --- .../kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt b/aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt index 4b65515d5..fbeddda14 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt @@ -165,6 +165,8 @@ class BlockFormatter(editor: AztecText, if (span != null) { removeBlockStyle(span.textFormat) + editableText.insert(selectionEnd, "\n") + editableText.delete(selectionEnd, selectionEnd+1) } removeBlockStyle(AztecTextFormat.FORMAT_PREFORMAT) @@ -857,6 +859,8 @@ class BlockFormatter(editor: AztecText, HeadingHandler.cloneHeading(editableText, headingSpan, alignmentRendering, lineStart, lineEnd) } + editableText.insert(end, "\n") + editableText.delete(end, end+1) } private fun liftBlock(textFormat: ITextFormat, start: Int, end: Int) { From 9f0780b82eaef0247f253908d6b409d6f1c2e1eb Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Fri, 15 Jul 2022 12:21:01 +0200 Subject: [PATCH 09/15] Add and remove new line in order to redraw a heading removed --- .../wordpress/aztec/formatting/BlockFormatter.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt b/aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt index fbeddda14..8713aef7f 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt @@ -165,8 +165,6 @@ class BlockFormatter(editor: AztecText, if (span != null) { removeBlockStyle(span.textFormat) - editableText.insert(selectionEnd, "\n") - editableText.delete(selectionEnd, selectionEnd+1) } removeBlockStyle(AztecTextFormat.FORMAT_PREFORMAT) @@ -433,6 +431,11 @@ class BlockFormatter(editor: AztecText, IAztecNestable.pullUp(editableText, editableText.getSpanStart(span), editableText.getSpanEnd(span), span.nestingLevel) editableText.removeSpan(span) + + if (span is AztecHeadingSpan) { + editableText.insert(spanEnd, "\n") + editableText.delete(spanEnd, spanEnd+1) + } } } } @@ -751,8 +754,7 @@ class BlockFormatter(editor: AztecText, // no similar blocks before us so, don't expand } else if (spansOnPreviousLine.nestingLevel != nestingLevel) { // other block is at a different nesting level so, don't expand - } else if (spansOnPreviousLine is AztecHeadingSpan - && spansOnPreviousLine.heading != (spanToApply as AztecHeadingSpan).heading) { + } else if (spansOnPreviousLine is AztecHeadingSpan && spanToApply is AztecHeadingSpan) { // Heading span is of different style so, don't expand } else if (!isWithinList) { // expand the start @@ -773,8 +775,7 @@ class BlockFormatter(editor: AztecText, // no similar blocks after us so, don't expand } else if (spanOnNextLine.nestingLevel != nestingLevel) { // other block is at a different nesting level so, don't expand - } else if (spanOnNextLine is AztecHeadingSpan - && spanOnNextLine.heading != (spanToApply as AztecHeadingSpan).heading) { + } else if (spanOnNextLine is AztecHeadingSpan && spanToApply is AztecHeadingSpan) { // Heading span is of different style so, don't expand } else if (!isWithinList) { // expand the end From e54627f2ced770ad5ff8dabaa0b639909db4ca13 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Fri, 15 Jul 2022 13:32:30 +0200 Subject: [PATCH 10/15] Fix placeholder update when heading applied above it --- .../aztec/placeholders/PlaceholderManager.kt | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt b/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt index 8fab1af96..50eddd051 100644 --- a/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt +++ b/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt @@ -110,11 +110,11 @@ class PlaceholderManager( positionToId.filter { it.elementPosition >= selectionStart - 1 }.forEach { - insertContentOverSpanWithId(it.uuid, it.elementPosition) + insertContentOverSpanWithId(it.uuid, it.elementTopOffset) } } - private suspend fun insertContentOverSpanWithId(uuid: String, currentPosition: Int? = null) { + private suspend fun insertContentOverSpanWithId(uuid: String, currentTopOffset: Int? = null) { var aztecAttributes: AztecAttributes? = null val predicate = object : AztecText.AttributePredicate { override fun matches(attrs: Attributes): Boolean { @@ -127,10 +127,10 @@ class PlaceholderManager( } val targetPosition = aztecText.getElementPosition(predicate) ?: return - insertInPosition(aztecAttributes ?: return, targetPosition, currentPosition) + insertInPosition(aztecAttributes ?: return, targetPosition, currentTopOffset) } - private suspend fun insertInPosition(attrs: AztecAttributes, targetPosition: Int, currentPosition: Int? = null) { + private suspend fun insertInPosition(attrs: AztecAttributes, targetPosition: Int, currentTopOffset: Int? = null) { if (!validateAttributes(attrs)) { return } @@ -139,15 +139,6 @@ class PlaceholderManager( val textViewLayout: Layout = aztecText.layout val parentTextViewRect = Rect() val targetLineOffset = textViewLayout.getLineForOffset(targetPosition) - if (currentPosition != null) { - if (targetLineOffset != 0 && currentPosition == targetPosition) { - return - } else { - positionToId.removeAll { - it.uuid == uuid - } - } - } textViewLayout.getLineBounds(targetLineOffset, parentTextViewRect) val parentTextViewLocation = intArrayOf(0, 0) @@ -157,6 +148,16 @@ class PlaceholderManager( parentTextViewRect.top += parentTextViewTopAndBottomOffset parentTextViewRect.bottom += parentTextViewTopAndBottomOffset + if (currentTopOffset != null) { + if (targetLineOffset != 0 && currentTopOffset == parentTextViewRect.top) { + return + } else { + positionToId.removeAll { + it.uuid == uuid + } + } + } + var box = container.findViewWithTag(uuid) val exists = box != null val adapter = adapters[type]!! @@ -178,7 +179,7 @@ class PlaceholderManager( box.tag = uuid box.setBackgroundColor(Color.TRANSPARENT) box.setOnTouchListener(adapter) - positionToId.add(Placeholder(targetPosition, uuid)) + positionToId.add(Placeholder(targetPosition, parentTextViewRect.top, uuid)) if (!exists && box.parent == null) { container.addView(box) adapter.onViewCreated(box, uuid) @@ -433,7 +434,7 @@ class PlaceholderManager( } } - data class Placeholder(val elementPosition: Int, val uuid: String) + data class Placeholder(val elementPosition: Int, val elementTopOffset: Int, val uuid: String) companion object { private const val DEFAULT_HTML_TAG = "placeholder" From 2f9f6d357d4ba3b67bc8f2a4a9a63ab517d83d8a Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Fri, 15 Jul 2022 14:21:44 +0200 Subject: [PATCH 11/15] Add comments --- .../kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt b/aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt index 8713aef7f..3476a5bce 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt @@ -432,6 +432,9 @@ class BlockFormatter(editor: AztecText, editableText.removeSpan(span) + // When a span with a different line height is replaced by a paragraph, the editor doesn't redraw + // correctly. This is a hack to force it to redraw by adding and removing a new line after the + // element. if (span is AztecHeadingSpan) { editableText.insert(spanEnd, "\n") editableText.delete(spanEnd, spanEnd+1) @@ -860,6 +863,8 @@ class BlockFormatter(editor: AztecText, HeadingHandler.cloneHeading(editableText, headingSpan, alignmentRendering, lineStart, lineEnd) } + // When a span with a different line height is attached, the editor doesn't redraw correctly. This is a hack to + // force it to redraw by adding and removing a new line after the element. editableText.insert(end, "\n") editableText.delete(end, end+1) } From b805732db164eafbb0553c6b0a7fc0e426caa466 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Mon, 18 Jul 2022 10:27:44 +0200 Subject: [PATCH 12/15] Remove over-optimization which makes placeholders not being redrawn --- .../aztec/placeholders/PlaceholderManager.kt | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt b/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt index 50eddd051..7e940ea7e 100644 --- a/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt +++ b/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt @@ -85,7 +85,7 @@ class PlaceholderManager( val drawable = buildPlaceholderDrawable(adapter, attrs) aztecText.insertMediaSpan(AztecPlaceholderSpan(aztecText.context, drawable, 0, attrs, this, aztecText, adapter, TAG = htmlTag)) - insertContentOverSpanWithId(attrs.getValue(UUID_ATTRIBUTE), null) + insertContentOverSpanWithId(attrs.getValue(UUID_ATTRIBUTE)) } /** @@ -110,11 +110,11 @@ class PlaceholderManager( positionToId.filter { it.elementPosition >= selectionStart - 1 }.forEach { - insertContentOverSpanWithId(it.uuid, it.elementTopOffset) + insertContentOverSpanWithId(it.uuid) } } - private suspend fun insertContentOverSpanWithId(uuid: String, currentTopOffset: Int? = null) { + private suspend fun insertContentOverSpanWithId(uuid: String) { var aztecAttributes: AztecAttributes? = null val predicate = object : AztecText.AttributePredicate { override fun matches(attrs: Attributes): Boolean { @@ -127,10 +127,10 @@ class PlaceholderManager( } val targetPosition = aztecText.getElementPosition(predicate) ?: return - insertInPosition(aztecAttributes ?: return, targetPosition, currentTopOffset) + insertInPosition(aztecAttributes ?: return, targetPosition) } - private suspend fun insertInPosition(attrs: AztecAttributes, targetPosition: Int, currentTopOffset: Int? = null) { + private suspend fun insertInPosition(attrs: AztecAttributes, targetPosition: Int) { if (!validateAttributes(attrs)) { return } @@ -148,14 +148,8 @@ class PlaceholderManager( parentTextViewRect.top += parentTextViewTopAndBottomOffset parentTextViewRect.bottom += parentTextViewTopAndBottomOffset - if (currentTopOffset != null) { - if (targetLineOffset != 0 && currentTopOffset == parentTextViewRect.top) { - return - } else { - positionToId.removeAll { - it.uuid == uuid - } - } + positionToId.removeAll { + it.uuid == uuid } var box = container.findViewWithTag(uuid) @@ -179,7 +173,7 @@ class PlaceholderManager( box.tag = uuid box.setBackgroundColor(Color.TRANSPARENT) box.setOnTouchListener(adapter) - positionToId.add(Placeholder(targetPosition, parentTextViewRect.top, uuid)) + positionToId.add(Placeholder(targetPosition, uuid)) if (!exists && box.parent == null) { container.addView(box) adapter.onViewCreated(box, uuid) @@ -434,7 +428,7 @@ class PlaceholderManager( } } - data class Placeholder(val elementPosition: Int, val elementTopOffset: Int, val uuid: String) + data class Placeholder(val elementPosition: Int, val uuid: String) companion object { private const val DEFAULT_HTML_TAG = "placeholder" From 48b219d10b4c56b04f55bd04570b67a2122f4948 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Mon, 18 Jul 2022 10:34:56 +0200 Subject: [PATCH 13/15] Make sure we clear all the views onDestroy --- .../org/wordpress/aztec/placeholders/PlaceholderManager.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt b/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt index 7e940ea7e..457dad818 100644 --- a/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt +++ b/media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt @@ -59,6 +59,12 @@ class PlaceholderManager( } fun onDestroy() { + positionToId.forEach { + container.findViewWithTag(it.uuid)?.let { placeholder -> + container.removeView(placeholder) + } + } + positionToId.clear() aztecText.contentChangeWatcher.unregisterObserver(this) adapters.values.forEach { it.onDestroy() } adapters.clear() From eb41f752fec156e717dac6a1158ad573e7bc515b Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Mon, 18 Jul 2022 10:39:58 +0200 Subject: [PATCH 14/15] Revert hacky fix to inserted heading size redraw --- .../org/wordpress/aztec/formatting/BlockFormatter.kt | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt b/aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt index 3476a5bce..60d6d4030 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/formatting/BlockFormatter.kt @@ -431,14 +431,6 @@ class BlockFormatter(editor: AztecText, IAztecNestable.pullUp(editableText, editableText.getSpanStart(span), editableText.getSpanEnd(span), span.nestingLevel) editableText.removeSpan(span) - - // When a span with a different line height is replaced by a paragraph, the editor doesn't redraw - // correctly. This is a hack to force it to redraw by adding and removing a new line after the - // element. - if (span is AztecHeadingSpan) { - editableText.insert(spanEnd, "\n") - editableText.delete(spanEnd, spanEnd+1) - } } } } @@ -863,10 +855,6 @@ class BlockFormatter(editor: AztecText, HeadingHandler.cloneHeading(editableText, headingSpan, alignmentRendering, lineStart, lineEnd) } - // When a span with a different line height is attached, the editor doesn't redraw correctly. This is a hack to - // force it to redraw by adding and removing a new line after the element. - editableText.insert(end, "\n") - editableText.delete(end, end+1) } private fun liftBlock(textFormat: ITextFormat, start: Int, end: Int) { From 8b6f7bc3ab3f39463d850fe1312681318303fc0b Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Mon, 18 Jul 2022 16:31:26 +0200 Subject: [PATCH 15/15] Make sure linebreaks are treated as ParagraphSpan.kt start/end for margin --- .../org/wordpress/aztec/spans/ParagraphSpan.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/spans/ParagraphSpan.kt b/aztec/src/main/kotlin/org/wordpress/aztec/spans/ParagraphSpan.kt index 8f22ae74f..1860b0a77 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/spans/ParagraphSpan.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/spans/ParagraphSpan.kt @@ -45,8 +45,18 @@ open class ParagraphSpan( val spanned = text as Spanned val spanStart = spanned.getSpanStart(this) val spanEnd = spanned.getSpanEnd(this) - val isFirstLine = start <= spanStart - val isLastLine = spanEnd <= end + val previousLineBreak = if (start > 1) { + text.substring(start-1, start) == "\n" + } else { + false + } + val followingLineBreak = if (end < text.length) { + text.substring(end, end + 1) == "\n" + } else { + false + } + val isFirstLine = start <= spanStart || previousLineBreak + val isLastLine = spanEnd <= end || followingLineBreak if (isFirstLine) { removeTopPadding = true fm.ascent -= paragraphStyle.verticalMargin