[go: nahoru, domu]

blob: 96d5158ae151af9b0057b79c65471cd61ffb0af7 [file] [log] [blame]
Andrei Rudenko2d7eb402020-06-11 11:11:52 +02001/*
2* Copyright 2020 The Android Open Source Project
3*
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7*
8* http://www.apache.org/licenses/LICENSE-2.0
9*
10* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
15*/
16package androidx.ui.desktop
17
Louis Pullen-Freilich4dc4dac2020-07-22 14:39:14 +010018import androidx.compose.ui.graphics.Color
19import androidx.compose.ui.graphics.toArgb
Louis Pullen-Freilichab194752020-07-21 22:21:22 +010020import androidx.compose.ui.text.AnnotatedString
21import androidx.compose.ui.text.ParagraphIntrinsics
22import androidx.compose.ui.text.Placeholder
23import androidx.compose.ui.text.SpanStyle
24import androidx.compose.ui.text.SpanStyleRange
25import androidx.compose.ui.text.TextStyle
26import androidx.compose.ui.text.font.Font
Louis Pullen-Freilicha7eeb102020-07-22 17:54:24 +010027import androidx.compose.ui.unit.Density
28import androidx.compose.ui.unit.TextUnit
Andrei Rudenko2d7eb402020-06-11 11:11:52 +020029import kotlin.math.ceil
Igor Demin4a0e9f02020-06-26 17:58:39 +030030import org.jetbrains.skija.paragraph.Paragraph
31import org.jetbrains.skija.paragraph.ParagraphBuilder
32import org.jetbrains.skija.paragraph.ParagraphStyle
33import org.jetbrains.skija.paragraph.TextStyle as SkTextStyle
Andrei Rudenko2d7eb402020-06-11 11:11:52 +020034
35internal class DesktopParagraphIntrinsics(
36 val text: String,
37 style: TextStyle,
38 spanStyles: List<SpanStyleRange>,
Nikolay Igotti05d96a42020-06-17 14:25:19 +030039 @Suppress("UNUSED_PARAMETER") placeholders: List<AnnotatedString.Range<Placeholder>>,
40 @Suppress("UNUSED_PARAMETER") density: Density,
Andrei Rudenko2d7eb402020-06-11 11:11:52 +020041 resourceLoader: Font.ResourceLoader
42) : ParagraphIntrinsics {
43
44 val fontLoader = resourceLoader as FontLoader
45 val para: Paragraph
46 init {
47 para = buildParagraph(text, style, spanStyles)
48
49 para.layout(Float.POSITIVE_INFINITY)
50 }
51
52 override val minIntrinsicWidth = ceil(para.getMinIntrinsicWidth())
53 override val maxIntrinsicWidth = ceil(para.getMaxIntrinsicWidth())
54
55 /**
56 * SkParagraph styles model doesn't match Compose's one.
57 * SkParagraph has only a stack-based push/pop styles interface that works great with Span
58 * trees.
59 * But in Compose we have a list of SpanStyles attached to arbitrary ranges, possibly
60 * overlapped, where a position in the list denotes style's priority
61 * We map Compose styles to SkParagraph styles by projecting every range start/end to single
62 * positions line and maintaining a list of active styles while building a paragraph. This list
63 * of active styles is being compiled into single SkParagraph's style for every chunk of text
64 */
65 private fun buildParagraph(
66 text: String,
67 textStyle: TextStyle,
68 spanStyles: List<SpanStyleRange>
69 ): Paragraph {
70 val cuts = spansToCuts(spanStyles)
71
72 var pos = 0
73 val ps = textStyleToParagraphStyle(textStyle)
74 val pb = ParagraphBuilder(ps, fontLoader.fonts)
75 // TODO: for some reasons paragraph style doesn't apply to text. maybe it's Skia bug,
76 // we need to investigate
77 val currentStyles = mutableListOf(Pair(0, textStyle.toSpanStyle()))
78 pb.pushStyle(textStylesToSkStyle(currentStyles)!!)
79
80 for (cut in cuts) {
81 pb.addText(text.subSequence(pos, cut.position).toString())
82 pb.popStyle()
83
84 when (cut.instruction) {
85 StyleInstruction.ADD -> currentStyles.add(Pair(cut.priority, cut.style))
86 StyleInstruction.REMOVE -> currentStyles.remove(Pair(cut.priority, cut.style))
87 }
88
89 textStylesToSkStyle(currentStyles)?.let { ts ->
90 pb.pushStyle(ts)
91 }
92 pos = cut.position
93 }
94
95 pb.addText(text.subSequence(pos, text.length).toString())
96
97 return pb.build()
98 }
99
100 private enum class StyleInstruction {
101 ADD,
102 REMOVE
103 }
104
105 private data class Cut(
106 val position: Int,
107 val priority: Int,
108 val style: SpanStyle,
109 val instruction: StyleInstruction
110 )
111
112 private fun spansToCuts(spans: List<SpanStyleRange>): List<Cut> {
113 val positions = mutableMapOf<Int, Cut>()
114 for ((i, span) in spans.withIndex()) {
115 positions[span.start] = Cut(span.start, i, span.item, StyleInstruction.ADD)
116 positions[span.end] = Cut(span.end, i, span.item, StyleInstruction.REMOVE)
117 }
118
119 val cuts = ArrayList<Cut>(positions.size)
120
121 for (v in positions.toSortedMap().values) {
122 cuts.add(v)
123 }
124 return cuts
125 }
126
127 private fun textStyleToParagraphStyle(style: TextStyle): ParagraphStyle {
128 val pStyle = ParagraphStyle()
129 val textStyle = SkTextStyle()
130 applyStyles(style.toSpanStyle(), textStyle)
131 pStyle.setTextStyle(textStyle)
132 return pStyle
133 }
134
135 private fun applyStyles(from: SpanStyle, to: SkTextStyle) {
136 if (from.color != Color.Unset) {
137 to.setColor(from.color.toArgb())
138 }
139 from.fontFamily?.let {
140 val fontFamilies = fontLoader.ensureRegistered(it)
141 to.setFontFamilies(fontFamilies.toTypedArray())
142 }
143 // TODO: support [TextUnit.Em]
144 if (from.fontSize != TextUnit.Inherit) {
145 to.setFontSize(from.fontSize.value)
146 }
147 }
148
149 private fun textStylesToSkStyle(styles: List<Pair<Int, SpanStyle>>): SkTextStyle? {
150 if (styles.isEmpty()) {
151 return null
152 }
153 val skStyle = SkTextStyle()
154 for (s in styles.sortedBy { (priority, _) -> priority }.map { (_, v) -> v }) {
155 applyStyles(s, skStyle)
156 }
157 return skStyle
158 }
159}