# 纵轴题：拆单位 + token 竖排。只做字符串，不包 ggplot。
# 各图仍自己写 geom_text；不要再包一层 add_ylab_*。
# 保护块优先于逐字，避免把 RD / R&D 拆开。
# 依赖：ggplot2 >= 3.5（geom_text 的 size.unit = "pt"）。

# 项目可在 source() 之后改写，例如 ylab_keep <- c(ylab_keep, "NDVI")
ylab_keep <- c("R&D", "RD", "AI", "GDP", "DEA", "CARS", "CCR")

# 末尾全角或半角括号视为单位；无括号则 unit 为空
split_ylab_unit <- function(s) {
  s <- as.character(s)[[1]]
  if (!nzchar(s)) {
    return(list(title = "", unit = ""))
  }
  m <- regexec("^(.+?)([（(][^）)]+[）)])\\s*$", s, perl = TRUE)
  parts <- regmatches(s, m)[[1]]
  if (length(parts) < 3) {
    return(list(title = s, unit = ""))
  }
  list(title = parts[[2]], unit = parts[[3]])
}

# 保护块与拉丁词整段保留，其余按字；用换行实现正向竖排
stack_ylab <- function(s, keep = ylab_keep) {
  s <- as.character(s)[[1]]
  if (!nzchar(s)) {
    return(s)
  }
  keep <- keep[order(nchar(keep), decreasing = TRUE)]
  esc <- function(x) {
    gsub("([][{}()^$+.*|?\\\\])", "\\\\\\1", x)
  }
  pat <- paste0(
    "(?:",
    paste(esc(keep), collapse = "|"),
    "|[A-Za-z]+|[0-9]+(?:\\.[0-9]+)?|%|.)"
  )
  tokens <- regmatches(s, gregexpr(pat, s, perl = TRUE))[[1]]
  paste(tokens, collapse = "\n")
}

# 字号/字体与 theme_tech 的 axis.text.y 对齐（11pt、simsun）
# geom_text 默认 size 是 mm，必须写 size.unit = "pt"，否则会小于轴标签
ylab_text_family <- "simsun"
ylab_text_size_pt <- 11
ylab_text_lineheight <- 0.95
# 单图：axis.title.y 与轴标签之间的间距（10pt 仍显贴，用约 1.5 字宽）
ylab_title_gap_pt <- 18
# 分面：geom_text 的 hjust 以轴题自身宽度（约 1 汉字）为单位
# 1 = 右缘贴轴线；再加约 4 位刻度 + 1.5 字间距
# 2.45 只够约 1.5 字宽，会与 13000 一类刻度重叠
ylab_stack_hjust <- 6
ylab_stack_margin_l <- 72

# 单图：labs(y = stack_ylab(title)) 之后、theme_tech 之后叠加
# 分面图 ylab 为空，这条只统一轴标签字体，不产生轴题
theme_ylab_stack <- ggplot2::theme(
  axis.title.y = ggplot2::element_text(
    angle = 0,
    family = ylab_text_family,
    size = ylab_text_size_pt,
    hjust = 0.5,
    vjust = 0.5,
    lineheight = ylab_text_lineheight,
    margin = ggplot2::margin(r = ylab_title_gap_pt, unit = "pt")
  ),
  axis.text.y = ggplot2::element_text(
    family = ylab_text_family,
    size = ylab_text_size_pt
  )
)
