Compose draw text with border and gradient - text

Is there any way to draw text like that in compose which will have border and shadow like drop. Font does not matter.
I have tried AnnotatedString to apply the same gradient to each letter with this code:
val colorStops = arrayOf(
0.0f to Color(0xffe2e145),
0.2f to Color(0xff7ab624)
)
Text(
text = buildAnnotatedString {
for (letter in "ANIMALS".toCharArray()) {
withStyle(
SpanStyle(
brush = Brush.linearGradient(colorStops = colorStops)
)
) {
append(letter)
}
}
},
fontSize = 60.sp
)
but it just gets parsed wrong and only gets applied to first letter only
Do you know what I could be doing wrong or is there a better way to do this?
One more thing would like text to be replacable.
If anyone has any ideas would be very grateful.
Edit
This is how stroke looks like on my Galaxy S10

You can use a Box to put more Text elements on top of another:
val colorStops = listOf(
Color(0xffe2e145),
Color(0xff7ab624)
)
Box(
modifier = Modifier
.fillMaxWidth()
) {
//Filled Text
Text(
text = longText,
color = Color(0xffe2e145),
fontSize = 64.sp
)
//Shadow text with a gradient color.
Text(
text = longText,
modifier = Modifier.offset(2.dp, 3.dp),
style = TextStyle(
brush = Brush.horizontalGradient(
colors = colorStops,
tileMode = TileMode.Mirror
)
),
fontSize = 64.sp
)
//Text with stroke border
Text(
text = longText,
color = Color(0xffB71C1C),
style = TextStyle.Default.copy(
fontSize = 64.sp,
drawStyle = Stroke(
miter = 10f,
width = 2f,
join = StrokeJoin.Round
)
)
)
}
Another option is to apply a shadow and a brush to the Text:
Box(
modifier = Modifier
.fillMaxWidth()
) {
//Shadow and Brush Text
Text(
text = longText,
style = TextStyle(
shadow = Shadow(
offset = Offset(8f, 8f),
blurRadius = 6f,
color = LightGray
),
brush = Brush.horizontalGradient(
colors = colorStops,
tileMode = TileMode.Mirror
)
),
fontSize = 64.sp
)
//Text with stroke border
Text(
text = longText,
color = Color(0xffB71C1C),
style = TextStyle.Default.copy(
fontSize = 64.sp,
drawStyle = Stroke(
miter = 10f,
width = 2f,
join = StrokeJoin.Round
)
)
)
}

Related

How to change color of mark on topoplot interactively?

I want to create interactive line- and topoplot depending on menu. I figured out how to make red the line chosen in menu, but it doesn't work for topoplot marks (black circles inside topoplot). I can change it manually (cmap[][4] = RGB{N0f8}(1.0,0.0,0.0)), but how to do that interactively?
f = Figure(backgroundcolor = RGBf(0.98, 0.98, 0.98), resolution = (1500, 700))
ax = Axis(f[1:3, 1], xlabel = "Time [s]", ylabel = "Voltage amplitude [µV]")
N = 1:length(pos) #1:4
hidespines!(ax, :t, :r)
GLMakie.xlims!(-0.3, 1.2)
hlines!(0, color = :gray, linewidth = 1)
vlines!(0, color = :gray, linewidth = 1)
times = range(-0.3, length=size(dat_e,2), step=1 ./ 128)
lines = Dict()
for i in N
mean_trial = mean(dat_e[i,:,:],dims=2)[:,1]
line = lines!(times, mean_trial, color = "black")
lines[i] = line
end
hidedecorations!(ax, label = false, ticks = false, ticklabels = false)
topo_axis = Axis(f[2, 2], width = 178, height = 178, aspect = DataAspect())
Makie.xlims!(low = -0.2, high = 1.2)
Makie.ylims!(low = -0.2, high = 1.2)
topoMatrix = eegHeadMatrix(pos[N], (0.5, 0.5), 0.5)
cmap = Observable(collect(ColorScheme(range(colorant"black", colorant"black", length=30))))
#cmap[][4] = RGB{N0f8}(1.0,0.0,0.0)
topo = eeg_topoplot!(topo_axis, N, # averaging all trial of 30 participants on Xth msec
raw.ch_names[1:30];
positions=pos, # produced automatically from ch_names
interpolation=NullInterpolator(),
enlarge=1,
#colorrange = (0, 1), # add the 0 for the white-first color
colormap = cmap[],
label_text=false)
hidedecorations!(current_axis())
hidespines!(current_axis())
num_prev = 0
menu = Menu(f[3, 2], options = raw.ch_names[1:30], default = nothing)#, default = "second")
on(menu.selection) do selected
if selected != nothing
num = findall(x->x==menu.selection[], raw.ch_names[1:30])[]
if num_prev != 0
lines[num_prev].color = "black"
cmap[][num] = RGB{N0f8}(1.0,0.0,0.0)
end
lines[num].color = "red"
cmap[][num] = RGB{N0f8}(1.0,0.0,0.0)
num_prev = num
end
end
notify(menu.selection)
#print(cmap[])
f
We solved this by putting this string at the end of the menu.selection section:
notify(lines)
It works, because lines() automatically creates Observable.

Jetpack Compose text background with indent

I'm new to jetpack compose and i wanat to know if this is possible to achive with jetpack compose. Giving a background to text is very easy with compose but if you want to give indent to background according to text position i don't know where to start an achive this effect.
Text background with indent
I did that:
#Composable
fun IBgText(
text: String,
modifier: Modifier = Modifier,
iBgStrokeWidth: Float? = null,
iBgStrokeColor: Color = Color.DarkGray,
iBgVerticalPadding: Dp = 0.dp,
iBgHorizontalPadding: Dp = 0.dp,
iBgCornerRadius: Dp = 0.dp,
iBgColor: Color = Color.Gray,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
style: TextStyle = LocalTextStyle.current
) {
val vSpace = with(LocalDensity.current) { iBgVerticalPadding.toPx() }
val hSpace = with(LocalDensity.current) { iBgHorizontalPadding.toPx() }
val corner = with(LocalDensity.current) { iBgCornerRadius.toPx() }
var path by remember { mutableStateOf(Path()) }
fun computePath(layoutResult: TextLayoutResult): Path {
fun isInnerCorner(
lr: TextLayoutResult,
i: Int,
top: Boolean = false,
right: Boolean
): Boolean {
if (top && i == 0) return false
if (!top && i == lr.lineCount - 1) return false
if (top && right) return lr.getLineRight(i - 1) > lr.getLineRight(i)
if (!top && right) return lr.getLineRight(i + 1) > lr.getLineRight(i)
if (top && !right) return lr.getLineLeft(i - 1) < lr.getLineLeft(i)
return lr.getLineLeft(i + 1) < lr.getLineLeft(i)
}
val nbLines = layoutResult.lineCount
for (i in 0 until nbLines) {
var top = layoutResult.getLineTop(i)
var bottom = layoutResult.getLineBottom(i)
val right = layoutResult.getLineRight(i) + hSpace
val topInner = isInnerCorner(layoutResult, i, top = true, right = true)
val bottomInner = isInnerCorner(layoutResult, i, top = false, right = true)
if (topInner) top += vSpace else top -= vSpace
if (bottomInner) bottom -= vSpace else bottom += vSpace
path.apply {
if (i == 0) {
moveTo(right - corner, top)
} else {
if (topInner) {
lineTo(right + corner, top)
} else {
lineTo(right - corner, top)
}
}
quadraticBezierTo(right, top, right, top + corner)
lineTo(right, bottom - corner)
if (bottomInner) {
quadraticBezierTo(right, bottom, right + corner, bottom)
} else {
quadraticBezierTo(right, bottom, right - corner, bottom)
}
}
}
for (i in (nbLines - 1) downTo 0) {
var top = layoutResult.getLineTop(i)
var bottom = layoutResult.getLineBottom(i)
val left = layoutResult.getLineLeft(i) - hSpace
val topInner = isInnerCorner(layoutResult, i, top = true, right = false)
val bottomInner = isInnerCorner(layoutResult, i, top = false, right = false)
if (topInner) top += vSpace else top -= vSpace
if (bottomInner) bottom -= vSpace else bottom += vSpace
path.apply {
if (bottomInner) {
lineTo(left - corner, bottom)
} else {
lineTo(left + corner, bottom)
}
quadraticBezierTo(left, bottom, left, bottom - corner)
lineTo(left, top + corner)
if (topInner) {
quadraticBezierTo(left, top, left - corner, top)
} else {
quadraticBezierTo(left, top, left + corner, top)
}
}
}
path.close()
return path
}
Text(
text,
onTextLayout = { layoutResult ->
path = computePath(layoutResult = layoutResult)
},
modifier = modifier.drawBehind {
drawPath(path, style = Fill, color = iBgColor)
if (iBgStrokeWidth != null) {
drawPath(path, style = Stroke(width = iBgStrokeWidth), color = iBgStrokeColor)
}
},
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
style = style
)
}
Usage:
#Preview
#Composable
fun Preview() {
Column (modifier = Modifier.fillMaxSize().background(Color.Black)){
IBgText(
text = "test\ntest test\ntest\ntest test",
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 6.dp, start = 10.dp, end = 10.dp, bottom = 6.dp),
iBgColor = Color.Blue.copy(alpha = .4f),
iBgStrokeWidth = 3f,
iBgCornerRadius = 2.dp,
iBgHorizontalPadding = 5.dp,
iBgStrokeColor = Color.Red,
iBgVerticalPadding = 1.dp,
color = Color.White
)
IBgText(
text = "This is a sample\ntext",
textAlign = TextAlign.Center,
modifier = Modifier.padding(20.dp, bottom = 6.dp).rotate(-15f),
iBgColor = Color(0xFFFFFFFF),
iBgCornerRadius = 2.dp,
iBgHorizontalPadding = 8.dp,
iBgVerticalPadding = 5.dp
)
IBgText(
text = "line 1\n-- line 2 --",
textAlign = TextAlign.End,
modifier = Modifier.padding(10.dp)
)
}
}
Result:
It is not pretty, but i guess it does the job.
onTextLayout = { layoutResult -> gives the boundaries of each line.
left, top, bottom, right
Then you can make a path going through each line.
I did a loop from the top right corner (1st line end) to the bottom right corner (last line end)
Then another loop from the bottom left (last line start) to the top left (first line start).
Then I added some rounded corners to match the picture.
Ps: I started Kotlin and Jetpack compose this week and spend all my Sunday on your question 😅

Draw border around image using pixi.js

In JS we can do this:
var ctx = canvas.getContext('2d'),
img = new Image;
img.onload = draw;
img.src = "http://i.stack.imgur.com/UFBxY.png";
function draw() {
var dArr = [-1,-1, 0,-1, 1,-1, -1,0, 1,0, -1,1, 0,1, 1,1], // offset array
s = 2, // thickness scale
i = 0, // iterator
x = 5, // final position
y = 5;
// draw images at offsets from the array scaled by s
for(; i < dArr.length; i += 2)
ctx.drawImage(img, x + dArr[i]*s, y + dArr[i+1]*s);
// fill with color
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = "red";
ctx.fillRect(0,0,canvas.width, canvas.height);
// draw original image in normal mode
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(img, x, y);
}
And it will draw a border around your image (PNG so it's not a rectangle border ).
How todo it in Pixi.js? Pixi.js doesn't seem to understand the transparent bits as well.

How to avoid arrow heads reaching into boxes?

I create SVG output with dot. When I set penwidth = 2 for nodes and edges, the arrowheads point slightly inside the boxes. Is there a way to avoid this? Maybe by drawing the nodes after the edges? Or by reducing the length of the edges/arrows to 95%? I've tried to find an attribute that would allow this, but could not find something that worked.
NOTE: Effect can't be seen when converting to png or jpg. Create SVG with dot -Tsvg file.dot > file.svg and view in browser.
This is my dot file:
digraph configure {
node [
shape = box,
fontname = "Courier",
style = "filled",
fillcolor = "#cccccc",
penwidth = 2
];
A [label = "configure.in"];
B [label = "autoconf\nGNU m4", fillcolor = ".7 .3 1.0"];
C [label = "configure"];
X [label = "cfg.hin", fillcolor = "#eeeeee"];
Y [label = "cfg.h", fillcolor = "#eeeeee"];
edge [
fontname = "Palatino-Italic",
fontsize = 9,
penwidth = 2,
arrowsize = 1
];
A -> B [label = " is input to"];
B -> C [label = " creates"];
subgraph cfg {
rank = same;
rankdir = LR;
X -> C [label = "is input to "];
C -> Y [label = "creates "];
}
}

Graphviz Selfloops overlap labels

I have a simple graphiz graph that I am trying to render with dot. It is a series of self-loops on a single state:
digraph FST {
size = "8.5,11";
label = "";
rankdir=LR;
bgcolor = "transparent";
center = 1;
rank = same;
margin = 0;
orientation = Portrait;
0 [label = "0", shape = doublecircle, style = bold, fontsize = 14, color="#339933"]
0 -> 0 [label = "a", fontsize = 14];
0 -> 0 [label = "b", fontsize = 14];
0 -> 0 [label = "cd", fontsize = 14];
0 -> 0 [label = "efg", fontsize = 14];
0 -> 0 [label = "a", fontsize = 14];
0 -> 0 [label = "q", fontsize = 14];
0 -> 0 [label = "xyzabc", fontsize = 14];
}
I compile this like:
$ cat self-loop.dot | dot -Tpng > self-loop.png
The edges however, overlap the labels and looks ridiculous. Is there some way to prevent the edges from doing this? To make them flare out more?
Resulting Graph
I have had no luck scouring forums or the graphviz documentation.
Multi-loops are a pain. Using ports (https://www.graphviz.org/doc/info/attrs.html#k:portPos) helps. Bur rankdir also causes problems, so I changed it.
digraph FST {
size = "8.5,11";
label = "";
//rankdir=LR; // note the change in rankdir!!
bgcolor = "transparent";
center = 1;
rank = same;
margin = 0;
orientation = Portrait;
0 [label = "0", shape = doublecircle, style = bold, fontsize = 14, color="#339933"]
0:nw -> 0:ne [label = "a", fontsize = 14];
0:nw -> 0:ne [label = "\nb", fontsize = 14];
0:nw -> 0:ne [label = "\ncd", fontsize = 14];
0:nw -> 0:ne [label = "\nefg ", fontsize = 14];
0:nw -> 0:ne [label = " a ", fontsize = 14];
0:nw -> 0:ne [label = " q ", fontsize = 14];
0:nw -> 0:ne [label = "xyzabc", fontsize = 14];
}
Giving:

Resources