///|
fn skia_resolved_text_layout_result(
input : @core.TextLayoutInput,
fallback : @core.TextLayoutResult,
layout~ : SkiaResolvedTextLayout,
) -> @core.TextLayoutResult {
if layout.segments.is_empty() {
return fallback
}
if layout.segments.length() == 1 && layout.segments[0].text == input.text {
return skia_text_layout_result(
layout.segments[0].font,
input,
fallback,
glyph_run=layout.segments[0].glyph_run,
)
}
let carets = skia_resolved_text_caret_positions(input.text, layout)
let measured = skia_layout_width_from_carets(carets)
match input.max_width {
Some(max_width) => {
let clamped : Array[Double] = []
for caret in carets {
clamped.push(caret.min(max_width))
}
{
size: @core.Size::new(
width=measured.min(max_width),
height=layout.metrics.height,
),
baseline: layout.metrics.baseline,
caret_positions: clamped,
}
}
None =>
{
size: @core.Size::new(width=measured, height=layout.metrics.height),
baseline: layout.metrics.baseline,
caret_positions: carets,
}
}
}
///|
fn skia_text_layout_result(
font : @skia_native.Font,
input : @core.TextLayoutInput,
fallback : @core.TextLayoutResult,
glyph_run~ : SkiaGlyphRun,
) -> @core.TextLayoutResult {
if !skia_font_can_measure_text(font, input.text) {
return fallback
}
let carets = skia_caret_positions(font, input.text, glyph_run~)
let measured = skia_layout_width_from_carets(carets)
let metrics = skia_text_metrics(
font,
fallback_baseline=fallback.baseline,
fallback_height=fallback.size.height,
)
match input.max_width {
Some(max_width) => {
let clamped : Array[Double] = []
for caret in carets {
clamped.push(caret.min(max_width))
}
{
size: @core.Size::new(
width=measured.min(max_width),
height=metrics.height,
),
baseline: metrics.baseline,
caret_positions: clamped,
}
}
None =>
{
size: @core.Size::new(width=measured, height=metrics.height),
baseline: metrics.baseline,
caret_positions: carets,
}
}
}
///|
fn skia_resolved_text_caret_positions(
text : String,
layout : SkiaResolvedTextLayout,
) -> Array[Double] {
let carets : Array[Double] = [0.0]
for segment in layout.segments {
let segment_carets = skia_caret_positions(
segment.font,
segment.text,
glyph_run=segment.glyph_run,
)
if segment_carets.length() > 1 {
for index in 1.. Bool {
text == "" || skia_measure_text_width(font, text) > 0.0
}
///|
fn skia_text_metrics(
font : @skia_native.Font,
fallback_baseline~ : Double,
fallback_height~ : Double,
) -> SkiaTextMetrics {
let metrics = font.metrics()
let ascent = metrics.ascent.to_double()
let descent = metrics.descent.to_double()
let leading = metrics.leading.to_double()
let measured_baseline = (0.0 - ascent).max(0.0)
let measured_height = (descent - ascent + leading)
.max(descent - ascent)
.max(0.0)
let baseline = if measured_baseline > 0.0 {
measured_baseline
} else {
fallback_baseline
}
let height = if measured_height > 0.0 {
measured_height
} else {
fallback_height
}
{ baseline, height: height.max(baseline) }
}
///|
fn skia_caret_positions(
font : @skia_native.Font,
text : String,
glyph_run? : SkiaGlyphRun,
) -> Array[Double] {
match glyph_run {
Some(run) =>
match skia_shaped_caret_positions(text, run) {
Some(carets) =>
if skia_shaped_caret_positions_are_usable(text, carets) {
return carets
}
None => ()
}
None => ()
}
let chars = text.to_array()
let carets : Array[Double] = [0.0]
for index in 1..<(chars.length() + 1) {
let prefix = String::from_array(chars[:index])
carets.push(skia_measure_text_width(font, prefix))
}
skia_stabilize_grapheme_cluster_carets(text, carets)
}
///|
fn skia_shaped_caret_positions_are_usable(
text : String,
carets : Array[Double],
) -> Bool {
let chars = text.to_array()
if carets.length() != chars.length() + 1 {
return false
}
let clusters = @render_common.text_grapheme_clusters(text)
if clusters.length() != chars.length() || chars.length() <= 2 {
return true
}
for index in 1..<(carets.length() - 1) {
if carets[index] > carets[index - 1] {
return true
}
}
false
}
///|
fn skia_layout_width_from_carets(carets : Array[Double]) -> Double {
if carets.is_empty() {
0.0
} else {
carets[carets.length() - 1]
}
}
///|
fn skia_complete_monotonic_carets(
text : String,
carets : Array[Double],
width : Double,
) -> Array[Double] {
let mut previous = 0.0
for index in 0.. Array[Double]? {
if run.clusters.is_empty() ||
run.positions.length() != run.clusters.length() ||
run.width <= 0.0 {
return None
}
let offsets = skia_utf8_char_offsets(text)
if offsets.length() <= 1 {
return Some([0.0])
}
let carets : Array[Double] = Array::new(capacity=offsets.length())
for _ in offsets {
carets.push(-1.0)
}
carets[0] = 0.0
carets[carets.length() - 1] = run.width
for index, cluster in run.clusters {
match skia_char_index_for_utf8_offset(offsets, cluster) {
Some(caret_index) => {
let x = run.positions[index].x.to_double()
if x >= 0.0 && x <= run.width {
let current = carets[caret_index]
if current < 0.0 || x < current {
carets[caret_index] = x
}
}
}
None => ()
}
}
Some(skia_complete_monotonic_carets(text, carets, run.width))
}
///|
fn skia_stabilize_grapheme_cluster_carets(
text : String,
carets : Array[Double],
) -> Array[Double] {
let chars = text.to_array()
if carets.length() != chars.length() + 1 {
return carets
}
let adjusted = carets.copy()
let boundaries = @core.TextGraphemeBoundaries::new(text~).boundaries()
for boundary_index in 0..<(boundaries.length() - 1) {
let start = boundaries[boundary_index]
let end = boundaries[boundary_index + 1]
if end > start + 1 {
skia_stabilize_caret_cluster(adjusted, start, end)
}
}
adjusted
}
///|
fn skia_stabilize_caret_cluster(
carets : Array[Double],
start : Int,
end : Int,
) -> Unit {
if start < 0 || end <= start + 1 || end >= carets.length() {
return ()
}
let cluster_start = carets[start]
for caret_index in (start + 1).. Array[Int] {
let offsets : Array[Int] = [0]
let mut offset = 0
for ch in text {
offset += @utf8.encode(ch.to_string()[:], bom=false).length()
offsets.push(offset)
}
offsets
}
///|
fn skia_char_index_for_utf8_offset(offsets : Array[Int], offset : Int) -> Int? {
if offset < 0 {
return None
}
for index, current in offsets {
if current == offset {
return Some(index)
}
if current > offset {
return Some(index)
}
}
None
}