You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							111 lines
						
					
					
						
							2.9 KiB
						
					
					
				
			
		
		
	
	
							111 lines
						
					
					
						
							2.9 KiB
						
					
					
				| import pyray as rl
 | |
| from openpilot.system.ui.lib.text_measure import measure_text_cached
 | |
| 
 | |
| 
 | |
| def _break_long_word(font: rl.Font, word: str, font_size: int, max_width: int) -> list[str]:
 | |
|   if not word:
 | |
|     return []
 | |
| 
 | |
|   parts = []
 | |
|   remaining = word
 | |
| 
 | |
|   while remaining:
 | |
|     if measure_text_cached(font, remaining, font_size).x <= max_width:
 | |
|       parts.append(remaining)
 | |
|       break
 | |
| 
 | |
|     # Binary search for the longest substring that fits
 | |
|     left, right = 1, len(remaining)
 | |
|     best_fit = 1
 | |
| 
 | |
|     while left <= right:
 | |
|       mid = (left + right) // 2
 | |
|       substring = remaining[:mid]
 | |
|       width = measure_text_cached(font, substring, font_size).x
 | |
| 
 | |
|       if width <= max_width:
 | |
|         best_fit = mid
 | |
|         left = mid + 1
 | |
|       else:
 | |
|         right = mid - 1
 | |
| 
 | |
|     # Add the part that fits
 | |
|     parts.append(remaining[:best_fit])
 | |
|     remaining = remaining[best_fit:]
 | |
| 
 | |
|   return parts
 | |
| 
 | |
| 
 | |
| _cache: dict[int, list[str]] = {}
 | |
| 
 | |
| 
 | |
| def wrap_text(font: rl.Font, text: str, font_size: int, max_width: int) -> list[str]:
 | |
|   key = hash((font.texture.id, text, font_size, max_width))
 | |
|   if key in _cache:
 | |
|     return _cache[key]
 | |
| 
 | |
|   if not text or max_width <= 0:
 | |
|     return []
 | |
| 
 | |
|   # Split text by newlines first to preserve explicit line breaks
 | |
|   paragraphs = text.split('\n')
 | |
|   all_lines: list[str] = []
 | |
| 
 | |
|   for paragraph in paragraphs:
 | |
|     # Handle empty paragraphs (preserve empty lines)
 | |
|     if not paragraph.strip():
 | |
|       all_lines.append("")
 | |
|       continue
 | |
| 
 | |
|     # Process each paragraph separately
 | |
|     words = paragraph.split()
 | |
|     if not words:
 | |
|       all_lines.append("")
 | |
|       continue
 | |
| 
 | |
|     lines: list[str] = []
 | |
|     current_line: list[str] = []
 | |
|     current_width = 0
 | |
|     space_width = int(measure_text_cached(font, " ", font_size).x)
 | |
| 
 | |
|     for word in words:
 | |
|       word_width = int(measure_text_cached(font, word, font_size).x)
 | |
| 
 | |
|       # Check if word alone exceeds max width (need to break the word)
 | |
|       if word_width > max_width:
 | |
|         # Finish current line if it has content
 | |
|         if current_line:
 | |
|           lines.append(" ".join(current_line))
 | |
|           current_line = []
 | |
|           current_width = 0
 | |
| 
 | |
|         # Break the long word into parts
 | |
|         lines.extend(_break_long_word(font, word, font_size, max_width))
 | |
|         continue
 | |
| 
 | |
|       # Calculate width if we add this word
 | |
|       needed_width = current_width
 | |
|       if current_line:  # Need space before word
 | |
|         needed_width += space_width
 | |
|       needed_width += word_width
 | |
| 
 | |
|       # Check if word fits on current line
 | |
|       if needed_width <= max_width:
 | |
|         current_line.append(word)
 | |
|         current_width = needed_width
 | |
|       else:
 | |
|         # Start new line with this word
 | |
|         if current_line:
 | |
|           lines.append(" ".join(current_line))
 | |
|         current_line = [word]
 | |
|         current_width = word_width
 | |
| 
 | |
|     # Add remaining words
 | |
|     if current_line:
 | |
|       lines.append(" ".join(current_line))
 | |
| 
 | |
|     # Add all lines from this paragraph
 | |
|     all_lines.extend(lines)
 | |
| 
 | |
|   _cache[key] = all_lines
 | |
|   return all_lines
 | |
| 
 |