react-native-enriched-markdown-scaffold 1.0.2-scaffold.7 → 1.0.2-scaffold.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +24 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +12 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +12 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +8 -4
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/InlineMathStyle.kt +16 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/MathStyle.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/HeadlessActionMode.kt +76 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +1 -1
- package/android/src/math/java/com/swmansion/enriched/markdown/renderer/MathInlineRenderer.kt +1 -0
- package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathInlineSpan.kt +13 -6
- package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathMeasureHelper.kt +2 -1
- package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +11 -4
- package/ios/attachments/ENRMMathInlineAttachment+macOS.m +2 -1
- package/ios/attachments/ENRMMathInlineAttachment.h +3 -0
- package/ios/attachments/ENRMMathInlineAttachment.m +2 -1
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +24 -0
- package/ios/internals/MeasurementCache.h +3 -2
- package/ios/math/ENRMRaTeXBridge.swift +23 -5
- package/ios/renderer/ENRMMathInlineRenderer.m +1 -0
- package/ios/styles/StyleConfig.h +4 -0
- package/ios/styles/StyleConfig.mm +24 -0
- package/ios/utils/StylePropsUtils.h +10 -0
- package/ios/views/ENRMMathContainerView.m +4 -2
- package/lib/module/EnrichedMarkdownNativeComponent.ts +2 -0
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +2 -0
- package/lib/module/normalizeMarkdownStyle.js +4 -2
- package/lib/module/normalizeMarkdownStyle.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.web.js +4 -2
- package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +2 -0
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +2 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyle.d.ts +22 -0
- package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +2 -0
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.d.ts +4 -0
- package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EnrichedMarkdownNativeComponent.ts +2 -0
- package/src/EnrichedMarkdownTextNativeComponent.ts +2 -0
- package/src/normalizeMarkdownStyle.ts +2 -0
- package/src/normalizeMarkdownStyle.web.ts +2 -0
- package/src/types/MarkdownStyle.ts +22 -0
- package/src/types/MarkdownStyleInternal.ts +2 -0
- package/src/types/MarkdownTextProps.ts +4 -0
|
@@ -1761,6 +1761,7 @@ struct EnrichedMarkdownMarkdownStyleMathStruct {
|
|
|
1761
1761
|
Float marginBottom{0.0};
|
|
1762
1762
|
std::string textAlign{};
|
|
1763
1763
|
std::string fontWeight{};
|
|
1764
|
+
Float glyphOverflow{0.0};
|
|
1764
1765
|
|
|
1765
1766
|
|
|
1766
1767
|
#ifdef RN_SERIALIZABLE_STATE
|
|
@@ -1776,6 +1777,7 @@ struct EnrichedMarkdownMarkdownStyleMathStruct {
|
|
|
1776
1777
|
result["marginBottom"] = marginBottom;
|
|
1777
1778
|
result["textAlign"] = textAlign;
|
|
1778
1779
|
result["fontWeight"] = fontWeight;
|
|
1780
|
+
result["glyphOverflow"] = glyphOverflow;
|
|
1779
1781
|
return result;
|
|
1780
1782
|
}
|
|
1781
1783
|
#endif
|
|
@@ -1816,6 +1818,10 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
|
|
|
1816
1818
|
if (tmp_fontWeight != map.end()) {
|
|
1817
1819
|
fromRawValue(context, tmp_fontWeight->second, result.fontWeight);
|
|
1818
1820
|
}
|
|
1821
|
+
auto tmp_glyphOverflow = map.find("glyphOverflow");
|
|
1822
|
+
if (tmp_glyphOverflow != map.end()) {
|
|
1823
|
+
fromRawValue(context, tmp_glyphOverflow->second, result.glyphOverflow);
|
|
1824
|
+
}
|
|
1819
1825
|
}
|
|
1820
1826
|
|
|
1821
1827
|
static inline std::string toString(const EnrichedMarkdownMarkdownStyleMathStruct &value) {
|
|
@@ -1832,6 +1838,7 @@ struct EnrichedMarkdownMarkdownStyleInlineMathStruct {
|
|
|
1832
1838
|
SharedColor color{};
|
|
1833
1839
|
Float paddingVertical{0.0};
|
|
1834
1840
|
std::string fontWeight{};
|
|
1841
|
+
Float glyphOverflow{0.0};
|
|
1835
1842
|
|
|
1836
1843
|
|
|
1837
1844
|
#ifdef RN_SERIALIZABLE_STATE
|
|
@@ -1842,6 +1849,7 @@ struct EnrichedMarkdownMarkdownStyleInlineMathStruct {
|
|
|
1842
1849
|
result["color"] = ::facebook::react::toDynamic(color);
|
|
1843
1850
|
result["paddingVertical"] = paddingVertical;
|
|
1844
1851
|
result["fontWeight"] = fontWeight;
|
|
1852
|
+
result["glyphOverflow"] = glyphOverflow;
|
|
1845
1853
|
return result;
|
|
1846
1854
|
}
|
|
1847
1855
|
#endif
|
|
@@ -1862,6 +1870,10 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
|
|
|
1862
1870
|
if (tmp_fontWeight != map.end()) {
|
|
1863
1871
|
fromRawValue(context, tmp_fontWeight->second, result.fontWeight);
|
|
1864
1872
|
}
|
|
1873
|
+
auto tmp_glyphOverflow = map.find("glyphOverflow");
|
|
1874
|
+
if (tmp_glyphOverflow != map.end()) {
|
|
1875
|
+
fromRawValue(context, tmp_glyphOverflow->second, result.glyphOverflow);
|
|
1876
|
+
}
|
|
1865
1877
|
}
|
|
1866
1878
|
|
|
1867
1879
|
static inline std::string toString(const EnrichedMarkdownMarkdownStyleInlineMathStruct &value) {
|
|
@@ -5807,6 +5819,7 @@ struct EnrichedMarkdownTextMarkdownStyleMathStruct {
|
|
|
5807
5819
|
Float marginBottom{0.0};
|
|
5808
5820
|
std::string textAlign{};
|
|
5809
5821
|
std::string fontWeight{};
|
|
5822
|
+
Float glyphOverflow{0.0};
|
|
5810
5823
|
|
|
5811
5824
|
|
|
5812
5825
|
#ifdef RN_SERIALIZABLE_STATE
|
|
@@ -5822,6 +5835,7 @@ struct EnrichedMarkdownTextMarkdownStyleMathStruct {
|
|
|
5822
5835
|
result["marginBottom"] = marginBottom;
|
|
5823
5836
|
result["textAlign"] = textAlign;
|
|
5824
5837
|
result["fontWeight"] = fontWeight;
|
|
5838
|
+
result["glyphOverflow"] = glyphOverflow;
|
|
5825
5839
|
return result;
|
|
5826
5840
|
}
|
|
5827
5841
|
#endif
|
|
@@ -5862,6 +5876,10 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
|
|
|
5862
5876
|
if (tmp_fontWeight != map.end()) {
|
|
5863
5877
|
fromRawValue(context, tmp_fontWeight->second, result.fontWeight);
|
|
5864
5878
|
}
|
|
5879
|
+
auto tmp_glyphOverflow = map.find("glyphOverflow");
|
|
5880
|
+
if (tmp_glyphOverflow != map.end()) {
|
|
5881
|
+
fromRawValue(context, tmp_glyphOverflow->second, result.glyphOverflow);
|
|
5882
|
+
}
|
|
5865
5883
|
}
|
|
5866
5884
|
|
|
5867
5885
|
static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleMathStruct &value) {
|
|
@@ -5878,6 +5896,7 @@ struct EnrichedMarkdownTextMarkdownStyleInlineMathStruct {
|
|
|
5878
5896
|
SharedColor color{};
|
|
5879
5897
|
Float paddingVertical{0.0};
|
|
5880
5898
|
std::string fontWeight{};
|
|
5899
|
+
Float glyphOverflow{0.0};
|
|
5881
5900
|
|
|
5882
5901
|
|
|
5883
5902
|
#ifdef RN_SERIALIZABLE_STATE
|
|
@@ -5888,6 +5907,7 @@ struct EnrichedMarkdownTextMarkdownStyleInlineMathStruct {
|
|
|
5888
5907
|
result["color"] = ::facebook::react::toDynamic(color);
|
|
5889
5908
|
result["paddingVertical"] = paddingVertical;
|
|
5890
5909
|
result["fontWeight"] = fontWeight;
|
|
5910
|
+
result["glyphOverflow"] = glyphOverflow;
|
|
5891
5911
|
return result;
|
|
5892
5912
|
}
|
|
5893
5913
|
#endif
|
|
@@ -5908,6 +5928,10 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
|
|
|
5908
5928
|
if (tmp_fontWeight != map.end()) {
|
|
5909
5929
|
fromRawValue(context, tmp_fontWeight->second, result.fontWeight);
|
|
5910
5930
|
}
|
|
5931
|
+
auto tmp_glyphOverflow = map.find("glyphOverflow");
|
|
5932
|
+
if (tmp_glyphOverflow != map.end()) {
|
|
5933
|
+
fromRawValue(context, tmp_glyphOverflow->second, result.glyphOverflow);
|
|
5934
|
+
}
|
|
5911
5935
|
}
|
|
5912
5936
|
|
|
5913
5937
|
static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleInlineMathStruct &value) {
|
package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt
CHANGED
|
@@ -13,6 +13,7 @@ import com.swmansion.enriched.markdown.spoiler.SpoilerCapable
|
|
|
13
13
|
import com.swmansion.enriched.markdown.spoiler.SpoilerOverlay
|
|
14
14
|
import com.swmansion.enriched.markdown.spoiler.SpoilerOverlayDrawer
|
|
15
15
|
import com.swmansion.enriched.markdown.utils.text.interaction.CheckboxTouchHelper
|
|
16
|
+
import com.swmansion.enriched.markdown.utils.text.view.HeadlessActionMode
|
|
16
17
|
import com.swmansion.enriched.markdown.utils.text.view.LinkLongPressMovementMethod
|
|
17
18
|
import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
|
|
18
19
|
import com.swmansion.enriched.markdown.utils.text.view.applySelectableState
|
|
@@ -128,6 +129,17 @@ class EnrichedMarkdownInternalText
|
|
|
128
129
|
onSelectionChangedCallback?.invoke(this, selEnd > selStart)
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
// See HeadlessActionMode: with the menu hidden, no toolbar window is created.
|
|
133
|
+
override fun startActionMode(
|
|
134
|
+
callback: ActionMode.Callback?,
|
|
135
|
+
type: Int,
|
|
136
|
+
): ActionMode? {
|
|
137
|
+
if (contextMenuHidden && type == ActionMode.TYPE_FLOATING && callback != null) {
|
|
138
|
+
return HeadlessActionMode.start(this, callback)
|
|
139
|
+
}
|
|
140
|
+
return super.startActionMode(callback, type)
|
|
141
|
+
}
|
|
142
|
+
|
|
131
143
|
/** Clears the selection and ends its ActionMode. */
|
|
132
144
|
fun clearSelection() {
|
|
133
145
|
clearTextSelection()
|
|
@@ -28,6 +28,7 @@ import com.swmansion.enriched.markdown.utils.common.emitLinkLayout
|
|
|
28
28
|
import com.swmansion.enriched.markdown.utils.common.emitSelectionChange
|
|
29
29
|
import com.swmansion.enriched.markdown.utils.text.TailFadeInAnimator
|
|
30
30
|
import com.swmansion.enriched.markdown.utils.text.interaction.CheckboxTouchHelper
|
|
31
|
+
import com.swmansion.enriched.markdown.utils.text.view.HeadlessActionMode
|
|
31
32
|
import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutCollector
|
|
32
33
|
import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutFragment
|
|
33
34
|
import com.swmansion.enriched.markdown.utils.text.view.LinkLongPressMovementMethod
|
|
@@ -336,6 +337,17 @@ class EnrichedMarkdownText
|
|
|
336
337
|
selectionReporter?.enabled = enabled
|
|
337
338
|
}
|
|
338
339
|
|
|
340
|
+
// See HeadlessActionMode: with the menu hidden, no toolbar window is created.
|
|
341
|
+
override fun startActionMode(
|
|
342
|
+
callback: ActionMode.Callback?,
|
|
343
|
+
type: Int,
|
|
344
|
+
): ActionMode? {
|
|
345
|
+
if (contextMenuHidden && type == ActionMode.TYPE_FLOATING && callback != null) {
|
|
346
|
+
return HeadlessActionMode.start(this, callback)
|
|
347
|
+
}
|
|
348
|
+
return super.startActionMode(callback, type)
|
|
349
|
+
}
|
|
350
|
+
|
|
339
351
|
/** Clears the selection (ending its ActionMode) and reports `active: false`. */
|
|
340
352
|
fun clearSelection() {
|
|
341
353
|
clearTextSelection()
|
|
@@ -337,9 +337,12 @@ object MeasurementStore {
|
|
|
337
337
|
tryRenderMarkdown(markdown, styleMap, context, md4cFlags, allowFontScaling, maxFontSizeMultiplier, imageRequestHeaders)
|
|
338
338
|
// Placeholders must be built with the same inlineMath.paddingVertical the real spans use,
|
|
339
339
|
// or the measured line box won't match the drawn one.
|
|
340
|
+
val inlineMathMap = styleMap.getMapOrNull("inlineMath")
|
|
341
|
+
val inlineMathOverflowDip = inlineMathMap.getFloatOrDefault("glyphOverflow", 0f)
|
|
340
342
|
spannable?.replaceMathSpansWithPlaceholders(
|
|
341
343
|
context,
|
|
342
|
-
PixelUtil.toPixelFromDIP(
|
|
344
|
+
PixelUtil.toPixelFromDIP(inlineMathMap.getFloatOrDefault("paddingVertical", 0f)) +
|
|
345
|
+
if (inlineMathOverflowDip > 0f) ceil(PixelUtil.toPixelFromDIP(inlineMathOverflowDip)) else 0f,
|
|
343
346
|
)
|
|
344
347
|
val textToMeasure = spannable ?: markdown
|
|
345
348
|
val (size, _) = measureWithLayout(width, textToMeasure, measurePaint, id)
|
|
@@ -456,7 +459,8 @@ object MeasurementStore {
|
|
|
456
459
|
for (i in mathSegmentIndices.indices) {
|
|
457
460
|
val metrics = mathResults[i]
|
|
458
461
|
mathHeightByIndex[mathSegmentIndices[i]] =
|
|
459
|
-
ceil(metrics.ascent + metrics.descent).toInt() + (style.mathStyle.padding * 2)
|
|
462
|
+
ceil(metrics.ascent + metrics.descent).toInt() + (style.mathStyle.padding * 2) +
|
|
463
|
+
(style.mathStyle.glyphOverflow * 2)
|
|
460
464
|
}
|
|
461
465
|
}
|
|
462
466
|
|
|
@@ -471,7 +475,7 @@ object MeasurementStore {
|
|
|
471
475
|
|
|
472
476
|
when (segment) {
|
|
473
477
|
is RenderedSegment.Text -> {
|
|
474
|
-
segment.styledText.replaceMathSpansWithPlaceholders(context, style.inlineMathStyle.
|
|
478
|
+
segment.styledText.replaceMathSpansWithPlaceholders(context, style.inlineMathStyle.boxPaddingVertical)
|
|
475
479
|
|
|
476
480
|
val layout = createStaticLayout(segment.styledText, fontSize, widthPx, id)
|
|
477
481
|
totalHeightPx += layout.height
|
|
@@ -579,7 +583,7 @@ object MeasurementStore {
|
|
|
579
583
|
style.imageRequestHeaders = imageRequestHeaders
|
|
580
584
|
measureRenderer.configure(style, context)
|
|
581
585
|
measureRenderer.renderDocument(ast, null).apply {
|
|
582
|
-
replaceMathSpansWithPlaceholders(context, style.inlineMathStyle.
|
|
586
|
+
replaceMathSpansWithPlaceholders(context, style.inlineMathStyle.boxPaddingVertical)
|
|
583
587
|
}
|
|
584
588
|
} catch (e: Exception) {
|
|
585
589
|
Log.w(TAG, "Failed to render markdown for measurement, falling back to raw text", e)
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
package com.swmansion.enriched.markdown.styles
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.ReadableMap
|
|
4
|
+
import kotlin.math.ceil
|
|
4
5
|
|
|
5
6
|
data class InlineMathStyle(
|
|
6
7
|
val color: Int,
|
|
7
8
|
val paddingVertical: Float,
|
|
8
9
|
val fontWeight: String,
|
|
10
|
+
val glyphOverflow: Float = 0f,
|
|
9
11
|
) {
|
|
12
|
+
/** Line-box padding the span reports: author padding plus the ink headroom. */
|
|
13
|
+
val boxPaddingVertical: Float get() = paddingVertical + glyphOverflow
|
|
14
|
+
|
|
10
15
|
companion object {
|
|
11
16
|
fun fromReadableMap(
|
|
12
17
|
map: ReadableMap,
|
|
@@ -15,7 +20,17 @@ data class InlineMathStyle(
|
|
|
15
20
|
val color = parser.parseColor(map, "color")
|
|
16
21
|
val paddingVertical = parser.toPixelFromDIP(map.getDouble("paddingVertical").toFloat())
|
|
17
22
|
val fontWeight = parser.parseString(map, "fontWeight", "normal")
|
|
18
|
-
|
|
23
|
+
val glyphOverflow = glyphOverflowPx(parser.parseOptionalDouble(map, "glyphOverflow").toFloat(), parser)
|
|
24
|
+
return InlineMathStyle(color, paddingVertical, fontWeight, glyphOverflow)
|
|
19
25
|
}
|
|
20
26
|
}
|
|
21
27
|
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* `glyphOverflow` in whole pixels. Rounded up so a measured box and the bitmap or
|
|
31
|
+
* canvas it holds always grow by the same integer amount on each side.
|
|
32
|
+
*/
|
|
33
|
+
internal fun glyphOverflowPx(
|
|
34
|
+
dip: Float,
|
|
35
|
+
parser: StyleParser,
|
|
36
|
+
): Float = if (dip <= 0f) 0f else ceil(parser.toPixelFromDIP(dip))
|
|
@@ -11,6 +11,7 @@ data class MathStyle(
|
|
|
11
11
|
val marginBottom: Float,
|
|
12
12
|
val textAlign: String,
|
|
13
13
|
val fontWeight: String,
|
|
14
|
+
val glyphOverflow: Float = 0f,
|
|
14
15
|
) {
|
|
15
16
|
companion object {
|
|
16
17
|
fun fromReadableMap(
|
|
@@ -25,6 +26,7 @@ data class MathStyle(
|
|
|
25
26
|
val marginBottom = parser.toPixelFromDIP(map.getDouble("marginBottom").toFloat())
|
|
26
27
|
val textAlign = parser.parseString(map, "textAlign", "center")
|
|
27
28
|
val fontWeight = parser.parseString(map, "fontWeight", "normal")
|
|
29
|
+
val glyphOverflow = glyphOverflowPx(parser.parseOptionalDouble(map, "glyphOverflow").toFloat(), parser)
|
|
28
30
|
return MathStyle(
|
|
29
31
|
fontSize,
|
|
30
32
|
color,
|
|
@@ -34,6 +36,7 @@ data class MathStyle(
|
|
|
34
36
|
marginBottom,
|
|
35
37
|
textAlign,
|
|
36
38
|
fontWeight,
|
|
39
|
+
glyphOverflow,
|
|
37
40
|
)
|
|
38
41
|
}
|
|
39
42
|
}
|
package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/HeadlessActionMode.kt
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.utils.text.view
|
|
2
|
+
|
|
3
|
+
import android.view.ActionMode
|
|
4
|
+
import android.view.Menu
|
|
5
|
+
import android.view.MenuInflater
|
|
6
|
+
import android.view.View
|
|
7
|
+
import android.widget.PopupMenu
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Stands in for the floating text-selection toolbar while `contextMenuHidden`
|
|
11
|
+
* is on. An empty menu only hides the AOSP FloatingToolbar; ROMs that draw
|
|
12
|
+
* their own selection popup (MIUI) still show it. Answering the Editor's
|
|
13
|
+
* startActionMode with this mode means no toolbar window is ever created.
|
|
14
|
+
*
|
|
15
|
+
* The callback is the Editor's TextActionModeCallback wrapping ours, so it
|
|
16
|
+
* still gets the full create/prepare/destroy lifecycle: Editor keeps the
|
|
17
|
+
* selection handles and its transient-state bookkeeping, and finish() clears
|
|
18
|
+
* the selection exactly as dismissing the real toolbar would.
|
|
19
|
+
*/
|
|
20
|
+
internal class HeadlessActionMode private constructor(
|
|
21
|
+
private val view: View,
|
|
22
|
+
private val callback: ActionMode.Callback,
|
|
23
|
+
) : ActionMode() {
|
|
24
|
+
// A real framework menu: Editor populates it with MenuItem calls
|
|
25
|
+
// (setAlphabeticShortcut, setShowAsAction, setIntent) a stub can't take.
|
|
26
|
+
private val menu: Menu = PopupMenu(view.context, view).menu
|
|
27
|
+
private var finished = false
|
|
28
|
+
|
|
29
|
+
override fun setTitle(title: CharSequence?) = Unit
|
|
30
|
+
|
|
31
|
+
override fun setTitle(resId: Int) = Unit
|
|
32
|
+
|
|
33
|
+
override fun setSubtitle(subtitle: CharSequence?) = Unit
|
|
34
|
+
|
|
35
|
+
override fun setSubtitle(resId: Int) = Unit
|
|
36
|
+
|
|
37
|
+
override fun setCustomView(view: View?) = Unit
|
|
38
|
+
|
|
39
|
+
override fun invalidate() {
|
|
40
|
+
if (!finished) callback.onPrepareActionMode(this, menu)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override fun invalidateContentRect() = Unit
|
|
44
|
+
|
|
45
|
+
override fun hide(duration: Long) = Unit
|
|
46
|
+
|
|
47
|
+
override fun finish() {
|
|
48
|
+
if (finished) return
|
|
49
|
+
finished = true
|
|
50
|
+
callback.onDestroyActionMode(this)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override fun getMenu(): Menu = menu
|
|
54
|
+
|
|
55
|
+
override fun getTitle(): CharSequence? = null
|
|
56
|
+
|
|
57
|
+
override fun getSubtitle(): CharSequence? = null
|
|
58
|
+
|
|
59
|
+
override fun getCustomView(): View? = null
|
|
60
|
+
|
|
61
|
+
override fun getMenuInflater(): MenuInflater = MenuInflater(view.context)
|
|
62
|
+
|
|
63
|
+
companion object {
|
|
64
|
+
/** Mirrors the framework: null when the callback declines to start. */
|
|
65
|
+
fun start(
|
|
66
|
+
view: View,
|
|
67
|
+
callback: ActionMode.Callback,
|
|
68
|
+
): ActionMode? {
|
|
69
|
+
val mode = HeadlessActionMode(view, callback)
|
|
70
|
+
mode.type = TYPE_FLOATING
|
|
71
|
+
if (!callback.onCreateActionMode(mode, mode.menu)) return null
|
|
72
|
+
callback.onPrepareActionMode(mode, mode.menu)
|
|
73
|
+
return mode
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -506,7 +506,7 @@ class TableContainerView(
|
|
|
506
506
|
.apply { configure(config, context) }
|
|
507
507
|
.renderDocument(MarkdownASTNode(NodeType.Document, children = listOf(paragraph)), null, null)
|
|
508
508
|
}
|
|
509
|
-
styledText.replaceMathSpansWithPlaceholders(context, config.inlineMathStyle.
|
|
509
|
+
styledText.replaceMathSpansWithPlaceholders(context, config.inlineMathStyle.boxPaddingVertical)
|
|
510
510
|
if (isHeader && styledText.isNotEmpty()) {
|
|
511
511
|
styledText.setSpan(HeaderTypefaceSpan(headerTypeface), 0, styledText.length, 33)
|
|
512
512
|
}
|
package/android/src/math/java/com/swmansion/enriched/markdown/renderer/MathInlineRenderer.kt
CHANGED
|
@@ -37,6 +37,7 @@ class MathInlineRenderer(
|
|
|
37
37
|
textColor = config.style.inlineMathStyle.color,
|
|
38
38
|
paddingVertical = config.style.inlineMathStyle.paddingVertical,
|
|
39
39
|
fontWeight = config.style.inlineMathStyle.fontWeight,
|
|
40
|
+
glyphOverflow = config.style.inlineMathStyle.glyphOverflow,
|
|
40
41
|
)
|
|
41
42
|
|
|
42
43
|
builder.setSpan(span, start, end, SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE)
|
|
@@ -16,6 +16,7 @@ class MathInlineSpan(
|
|
|
16
16
|
private val textColor: Int,
|
|
17
17
|
private val paddingVertical: Float,
|
|
18
18
|
private val fontWeight: String = "normal",
|
|
19
|
+
private val glyphOverflow: Float = 0f,
|
|
19
20
|
) : ReplacementSpan() {
|
|
20
21
|
private var cachedBitmap: Bitmap? = null
|
|
21
22
|
private var cachedWidth = 0
|
|
@@ -31,18 +32,24 @@ class MathInlineSpan(
|
|
|
31
32
|
val displayList = RaTeXEngine.parseBlocking(latex, displayMode = false, color = textColor)
|
|
32
33
|
val renderer = RaTeXRenderer(displayList, fontSize, ratexTypefaceLoader(fontWeight))
|
|
33
34
|
|
|
34
|
-
cachedWidth = renderer.widthPx.toInt().coerceAtLeast(1)
|
|
35
|
+
cachedWidth = ceil(renderer.widthPx).toInt().coerceAtLeast(1)
|
|
35
36
|
mathAscent = renderer.heightPx
|
|
36
37
|
mathDescent = renderer.depthPx
|
|
37
38
|
|
|
39
|
+
// RaTeX reports TeX metric boxes, which digit and capital outlines (and the
|
|
40
|
+
// fake-bold stroke) overshoot; the bitmap keeps glyphOverflow px of headroom
|
|
41
|
+
// on both sides so that ink is not cut off.
|
|
42
|
+
val overflowPx = glyphOverflow.toInt()
|
|
38
43
|
val bitmap =
|
|
39
44
|
Bitmap.createBitmap(
|
|
40
45
|
cachedWidth,
|
|
41
|
-
ceil(renderer.totalHeightPx).toInt().coerceAtLeast(1),
|
|
46
|
+
ceil(renderer.totalHeightPx).toInt().coerceAtLeast(1) + overflowPx * 2,
|
|
42
47
|
Bitmap.Config.ARGB_8888,
|
|
43
48
|
)
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
val canvas = Canvas(bitmap)
|
|
51
|
+
canvas.translate(0f, overflowPx.toFloat())
|
|
52
|
+
renderer.draw(canvas)
|
|
46
53
|
cachedBitmap = bitmap
|
|
47
54
|
} catch (_: Exception) {
|
|
48
55
|
renderFailed = true
|
|
@@ -66,9 +73,9 @@ class MathInlineSpan(
|
|
|
66
73
|
// Must stay identical to MathInlinePlaceholderSpan.getSize: that span measures
|
|
67
74
|
// this one off the main thread, and a box that disagrees by even a pixel either
|
|
68
75
|
// clips the formula or eats the padding this field is here to add.
|
|
69
|
-
ascent = -ceil(mathAscent + paddingVertical).toInt()
|
|
76
|
+
ascent = -ceil(mathAscent + (paddingVertical + glyphOverflow)).toInt()
|
|
70
77
|
top = ascent
|
|
71
|
-
descent = ceil(mathDescent + paddingVertical).toInt()
|
|
78
|
+
descent = ceil(mathDescent + (paddingVertical + glyphOverflow)).toInt()
|
|
72
79
|
bottom = descent
|
|
73
80
|
}
|
|
74
81
|
|
|
@@ -88,7 +95,7 @@ class MathInlineSpan(
|
|
|
88
95
|
) {
|
|
89
96
|
prepareResources()
|
|
90
97
|
cachedBitmap?.let {
|
|
91
|
-
val bitmapY = y - ceil(mathAscent)
|
|
98
|
+
val bitmapY = y - ceil(mathAscent) - glyphOverflow
|
|
92
99
|
canvas.drawBitmap(it, x, bitmapY, paint)
|
|
93
100
|
}
|
|
94
101
|
}
|
|
@@ -5,6 +5,7 @@ import android.util.Log
|
|
|
5
5
|
import io.ratex.RaTeXEngine
|
|
6
6
|
import io.ratex.RaTeXFontLoader
|
|
7
7
|
import io.ratex.RaTeXRenderer
|
|
8
|
+
import kotlin.math.ceil
|
|
8
9
|
|
|
9
10
|
object MathMeasureHelper {
|
|
10
11
|
@JvmStatic
|
|
@@ -37,7 +38,7 @@ object MathMeasureHelper {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
return MathMetrics(
|
|
40
|
-
renderer.widthPx.toInt(),
|
|
41
|
+
ceil(renderer.widthPx).toInt().coerceAtLeast(1),
|
|
41
42
|
renderer.heightPx,
|
|
42
43
|
renderer.depthPx,
|
|
43
44
|
)
|
|
@@ -58,7 +58,7 @@ class MathContainerView(
|
|
|
58
58
|
else -> Gravity.CENTER_HORIZONTAL
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
private val mathView = RaTeXCanvasView(context)
|
|
61
|
+
private val mathView = RaTeXCanvasView(context, mathStyle.glyphOverflow)
|
|
62
62
|
|
|
63
63
|
init {
|
|
64
64
|
setBackgroundColor(mathStyle.backgroundColor)
|
|
@@ -139,8 +139,11 @@ class MathContainerView(
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
// glyphOverflow px of headroom above and below the RaTeX box: digit and capital
|
|
143
|
+
// outlines (and the fake-bold stroke) overshoot it, and this view clips to its size.
|
|
142
144
|
private class RaTeXCanvasView(
|
|
143
145
|
context: Context,
|
|
146
|
+
private val glyphOverflow: Float,
|
|
144
147
|
) : View(context) {
|
|
145
148
|
var renderer: RaTeXRenderer? = null
|
|
146
149
|
|
|
@@ -153,12 +156,16 @@ class MathContainerView(
|
|
|
153
156
|
?: return setMeasuredDimension(0, 0)
|
|
154
157
|
setMeasuredDimension(
|
|
155
158
|
ceil(currentRenderer.widthPx).toInt().coerceAtLeast(1),
|
|
156
|
-
ceil(currentRenderer.totalHeightPx).toInt().coerceAtLeast(1),
|
|
159
|
+
ceil(currentRenderer.totalHeightPx).toInt().coerceAtLeast(1) + (glyphOverflow * 2).toInt(),
|
|
157
160
|
)
|
|
158
161
|
}
|
|
159
162
|
|
|
160
163
|
override fun onDraw(canvas: Canvas) {
|
|
161
|
-
renderer
|
|
164
|
+
val currentRenderer = renderer ?: return
|
|
165
|
+
canvas.save()
|
|
166
|
+
canvas.translate(0f, glyphOverflow)
|
|
167
|
+
currentRenderer.draw(canvas)
|
|
168
|
+
canvas.restore()
|
|
162
169
|
}
|
|
163
170
|
}
|
|
164
171
|
|
|
@@ -175,7 +182,7 @@ class MathContainerView(
|
|
|
175
182
|
mode = MathRenderMode.Display,
|
|
176
183
|
)
|
|
177
184
|
val metrics = MathMeasureHelper.measure(context, listOf(request)).first()
|
|
178
|
-
return ceil(metrics.ascent + metrics.descent).toInt() + (mathStyle.padding * 2)
|
|
185
|
+
return ceil(metrics.ascent + metrics.descent).toInt() + (mathStyle.padding * 2) + (mathStyle.glyphOverflow * 2)
|
|
179
186
|
}
|
|
180
187
|
|
|
181
188
|
private const val TAG = "MathContainerView"
|
|
@@ -14,6 +14,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
14
14
|
/// Typeset the glyphs at bold weight (synthetic — see ENRMRaTeXBridge).
|
|
15
15
|
@property (nonatomic, assign) BOOL bold;
|
|
16
16
|
|
|
17
|
+
/// Ink headroom above and below the formula box (`inlineMath.glyphOverflow`, points).
|
|
18
|
+
@property (nonatomic, assign) CGFloat glyphOverflow;
|
|
19
|
+
|
|
17
20
|
/// YES if block math (`$$...$$`), NO for inline (`$...$`).
|
|
18
21
|
@property (nonatomic, assign) BOOL displayMode;
|
|
19
22
|
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
displayMode:NO
|
|
43
43
|
fontSize:self.fontSize
|
|
44
44
|
color:color
|
|
45
|
-
bold:self.bold
|
|
45
|
+
bold:self.bold
|
|
46
|
+
glyphOverflow:self.glyphOverflow];
|
|
46
47
|
if (!result) {
|
|
47
48
|
_fallbackSource = ENRMMathFallbackString(self.latex, @"$", self.fontSize, color);
|
|
48
49
|
UIFont *font = [_fallbackSource attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL];
|
|
@@ -1761,6 +1761,7 @@ struct EnrichedMarkdownMarkdownStyleMathStruct {
|
|
|
1761
1761
|
Float marginBottom{0.0};
|
|
1762
1762
|
std::string textAlign{};
|
|
1763
1763
|
std::string fontWeight{};
|
|
1764
|
+
Float glyphOverflow{0.0};
|
|
1764
1765
|
|
|
1765
1766
|
|
|
1766
1767
|
#ifdef RN_SERIALIZABLE_STATE
|
|
@@ -1776,6 +1777,7 @@ struct EnrichedMarkdownMarkdownStyleMathStruct {
|
|
|
1776
1777
|
result["marginBottom"] = marginBottom;
|
|
1777
1778
|
result["textAlign"] = textAlign;
|
|
1778
1779
|
result["fontWeight"] = fontWeight;
|
|
1780
|
+
result["glyphOverflow"] = glyphOverflow;
|
|
1779
1781
|
return result;
|
|
1780
1782
|
}
|
|
1781
1783
|
#endif
|
|
@@ -1816,6 +1818,10 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
|
|
|
1816
1818
|
if (tmp_fontWeight != map.end()) {
|
|
1817
1819
|
fromRawValue(context, tmp_fontWeight->second, result.fontWeight);
|
|
1818
1820
|
}
|
|
1821
|
+
auto tmp_glyphOverflow = map.find("glyphOverflow");
|
|
1822
|
+
if (tmp_glyphOverflow != map.end()) {
|
|
1823
|
+
fromRawValue(context, tmp_glyphOverflow->second, result.glyphOverflow);
|
|
1824
|
+
}
|
|
1819
1825
|
}
|
|
1820
1826
|
|
|
1821
1827
|
static inline std::string toString(const EnrichedMarkdownMarkdownStyleMathStruct &value) {
|
|
@@ -1832,6 +1838,7 @@ struct EnrichedMarkdownMarkdownStyleInlineMathStruct {
|
|
|
1832
1838
|
SharedColor color{};
|
|
1833
1839
|
Float paddingVertical{0.0};
|
|
1834
1840
|
std::string fontWeight{};
|
|
1841
|
+
Float glyphOverflow{0.0};
|
|
1835
1842
|
|
|
1836
1843
|
|
|
1837
1844
|
#ifdef RN_SERIALIZABLE_STATE
|
|
@@ -1842,6 +1849,7 @@ struct EnrichedMarkdownMarkdownStyleInlineMathStruct {
|
|
|
1842
1849
|
result["color"] = ::facebook::react::toDynamic(color);
|
|
1843
1850
|
result["paddingVertical"] = paddingVertical;
|
|
1844
1851
|
result["fontWeight"] = fontWeight;
|
|
1852
|
+
result["glyphOverflow"] = glyphOverflow;
|
|
1845
1853
|
return result;
|
|
1846
1854
|
}
|
|
1847
1855
|
#endif
|
|
@@ -1862,6 +1870,10 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
|
|
|
1862
1870
|
if (tmp_fontWeight != map.end()) {
|
|
1863
1871
|
fromRawValue(context, tmp_fontWeight->second, result.fontWeight);
|
|
1864
1872
|
}
|
|
1873
|
+
auto tmp_glyphOverflow = map.find("glyphOverflow");
|
|
1874
|
+
if (tmp_glyphOverflow != map.end()) {
|
|
1875
|
+
fromRawValue(context, tmp_glyphOverflow->second, result.glyphOverflow);
|
|
1876
|
+
}
|
|
1865
1877
|
}
|
|
1866
1878
|
|
|
1867
1879
|
static inline std::string toString(const EnrichedMarkdownMarkdownStyleInlineMathStruct &value) {
|
|
@@ -5807,6 +5819,7 @@ struct EnrichedMarkdownTextMarkdownStyleMathStruct {
|
|
|
5807
5819
|
Float marginBottom{0.0};
|
|
5808
5820
|
std::string textAlign{};
|
|
5809
5821
|
std::string fontWeight{};
|
|
5822
|
+
Float glyphOverflow{0.0};
|
|
5810
5823
|
|
|
5811
5824
|
|
|
5812
5825
|
#ifdef RN_SERIALIZABLE_STATE
|
|
@@ -5822,6 +5835,7 @@ struct EnrichedMarkdownTextMarkdownStyleMathStruct {
|
|
|
5822
5835
|
result["marginBottom"] = marginBottom;
|
|
5823
5836
|
result["textAlign"] = textAlign;
|
|
5824
5837
|
result["fontWeight"] = fontWeight;
|
|
5838
|
+
result["glyphOverflow"] = glyphOverflow;
|
|
5825
5839
|
return result;
|
|
5826
5840
|
}
|
|
5827
5841
|
#endif
|
|
@@ -5862,6 +5876,10 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
|
|
|
5862
5876
|
if (tmp_fontWeight != map.end()) {
|
|
5863
5877
|
fromRawValue(context, tmp_fontWeight->second, result.fontWeight);
|
|
5864
5878
|
}
|
|
5879
|
+
auto tmp_glyphOverflow = map.find("glyphOverflow");
|
|
5880
|
+
if (tmp_glyphOverflow != map.end()) {
|
|
5881
|
+
fromRawValue(context, tmp_glyphOverflow->second, result.glyphOverflow);
|
|
5882
|
+
}
|
|
5865
5883
|
}
|
|
5866
5884
|
|
|
5867
5885
|
static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleMathStruct &value) {
|
|
@@ -5878,6 +5896,7 @@ struct EnrichedMarkdownTextMarkdownStyleInlineMathStruct {
|
|
|
5878
5896
|
SharedColor color{};
|
|
5879
5897
|
Float paddingVertical{0.0};
|
|
5880
5898
|
std::string fontWeight{};
|
|
5899
|
+
Float glyphOverflow{0.0};
|
|
5881
5900
|
|
|
5882
5901
|
|
|
5883
5902
|
#ifdef RN_SERIALIZABLE_STATE
|
|
@@ -5888,6 +5907,7 @@ struct EnrichedMarkdownTextMarkdownStyleInlineMathStruct {
|
|
|
5888
5907
|
result["color"] = ::facebook::react::toDynamic(color);
|
|
5889
5908
|
result["paddingVertical"] = paddingVertical;
|
|
5890
5909
|
result["fontWeight"] = fontWeight;
|
|
5910
|
+
result["glyphOverflow"] = glyphOverflow;
|
|
5891
5911
|
return result;
|
|
5892
5912
|
}
|
|
5893
5913
|
#endif
|
|
@@ -5908,6 +5928,10 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
|
|
|
5908
5928
|
if (tmp_fontWeight != map.end()) {
|
|
5909
5929
|
fromRawValue(context, tmp_fontWeight->second, result.fontWeight);
|
|
5910
5930
|
}
|
|
5931
|
+
auto tmp_glyphOverflow = map.find("glyphOverflow");
|
|
5932
|
+
if (tmp_glyphOverflow != map.end()) {
|
|
5933
|
+
fromRawValue(context, tmp_glyphOverflow->second, result.glyphOverflow);
|
|
5934
|
+
}
|
|
5911
5935
|
}
|
|
5912
5936
|
|
|
5913
5937
|
static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleInlineMathStruct &value) {
|
|
@@ -144,8 +144,9 @@ template <typename StyleStruct> inline size_t computeStyleFingerprint(const Styl
|
|
|
144
144
|
hashTextLayout(s.table);
|
|
145
145
|
hashFields(s.table.headerFontFamily, s.table.cellPaddingHorizontal, s.table.cellPaddingVertical, s.table.borderWidth,
|
|
146
146
|
s.table.borderRadius, s.table.horizontalOverflow);
|
|
147
|
-
hashFields(s.math.fontSize, s.math.padding, s.math.marginTop, s.math.marginBottom, s.math.textAlign
|
|
148
|
-
|
|
147
|
+
hashFields(s.math.fontSize, s.math.padding, s.math.marginTop, s.math.marginBottom, s.math.textAlign,
|
|
148
|
+
s.math.glyphOverflow);
|
|
149
|
+
hashFields(s.inlineMath.paddingVertical, s.inlineMath.glyphOverflow);
|
|
149
150
|
hashFields(s.taskList.checkboxSize, s.taskList.checkboxBorderRadius);
|
|
150
151
|
|
|
151
152
|
return h;
|