-- Copyright 2026 Open-Guji (https://github.com/open-guji) -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. -- -- 文件名: render_banxin.lua - 版心(鱼尾)绘制模块 -- 层级: 第三阶段 - 渲染层 (Stage 3: Render Layer) -- -- 【模块功能 / Module Purpose】 -- 本模块负责绘制古籍排版中的"版心"(中间的分隔列),包括: -- 1. 绘制版心列的边框(与普通列边框样式相同) -- 2. 在版心内绘制两条水平分隔线,将版心分为三个区域 -- 3. 在版心各区域绘制竖排文字 -- 4. 支持自定义三个区域的高度比例(默认 0.28:0.56:0.16) -- 5. 支持各区域的背景色和字体颜色 -- -- 【整体架构】 -- draw_from_layout(p_head, layout, runtime) -- ├─ render_border() - 绘制边框 -- ├─ draw_dividers() - 绘制分隔线 -- ├─ draw_yuwei() - 绘制鱼尾 -- ├─ render_section_background() - 绘制区域背景色 -- ├─ render_text_section() - 绘制文字 -- ├─ render_middle_section_from_layout() - 绘制中部文字 -- ├─ render_page_number_from_layout() - 绘制页码 -- └─ render_debug_rects() - 调试矩形 -- -- ============================================================================ -- Load dependencies local constants = package.loaded['core.luatex-cn-constants'] or require('core.luatex-cn-constants') local D = constants.D local utils = package.loaded['util.luatex-cn-utils'] or require('util.luatex-cn-utils') local text_position = package.loaded['core.luatex-cn-render-position'] or require('luatex-cn-render-position') local yuwei = package.loaded['banxin.luatex-cn-banxin-render-yuwei'] or require('banxin.luatex-cn-banxin-render-yuwei') local banxin_layout = package.loaded['banxin.luatex-cn-banxin-layout'] or require('banxin.luatex-cn-banxin-layout') local style_registry = package.loaded['util.luatex-cn-style-registry'] or require('util.luatex-cn-style-registry') local setting_stack = package.loaded['util.luatex-cn-setting-stack'] or require('util.luatex-cn-setting-stack') -- Register banxin module if debug module is available local debug = package.loaded['debug.luatex-cn-debug'] or require('debug.luatex-cn-debug') local dbg = debug.get_debugger('banxin') -- Conversion factor from scaled points to PDF big points local sp_to_bp = utils.sp_to_bp -- ============================================================================ -- Helper Functions (纯函数,只计算不产生副作用) -- ============================================================================ -- Reuse shared helpers from banxin-layout module local count_utf8_chars = banxin_layout.count_utf8_chars local calculate_yuwei_dimensions = banxin_layout.calculate_yuwei_dimensions local calculate_yuwei_total_height = banxin_layout.calculate_yuwei_total_height --- 将节点链插入到链表头部 -- @param p_head (node) 当前链表头 -- @param glyph_chain (node) 要插入的节点链 -- @return (node) 新的链表头 local function prepend_chain(p_head, glyph_chain) if not glyph_chain then return p_head end local chain_tail = glyph_chain while D.getnext(chain_tail) do chain_tail = D.getnext(chain_tail) end D.setlink(chain_tail, p_head) return glyph_chain end -- ============================================================================ -- PDF Literal Generators (生成 PDF 指令字符串) -- ============================================================================ --- 生成边框矩形的 PDF literal -- @param x (number) X 坐标 (sp) -- @param y (number) Y 坐标 (sp) -- @param width (number) 宽度 (sp) -- @param height (number) 高度 (sp, 正值) -- @param thickness (number) 线宽 (sp) -- @param color_str (string) RGB 颜色字符串 -- @return (string) PDF literal 字符串 local function create_border_literal(x, y, width, height, thickness, color_str) local x_bp = x * sp_to_bp local y_bp = y * sp_to_bp local width_bp = width * sp_to_bp local height_bp = -height * sp_to_bp -- Negative because Y goes downward local thickness_bp = thickness * sp_to_bp return string.format( "q %.2f w %s RG %.4f %.4f %.4f %.4f re S Q", thickness_bp, color_str, x_bp, y_bp, width_bp, height_bp ) end --- 生成填充矩形的 PDF literal (用于背景色) -- @param x (number) X 坐标 (sp) -- @param y (number) Y 坐标 (sp) -- @param width (number) 宽度 (sp) -- @param height (number) 高度 (sp, 正值,向下) -- @param color_str (string) RGB 颜色字符串 (空格分隔,0-1范围) -- @return (string) PDF literal 字符串 local function create_fill_rect_literal(x, y, width, height, color_str) local x_bp = x * sp_to_bp local y_bp = y * sp_to_bp local width_bp = width * sp_to_bp local height_bp = -height * sp_to_bp return string.format( "q %s rg %.4f %.4f %.4f %.4f re f Q", color_str, x_bp, y_bp, width_bp, height_bp ) end --- 生成水平分隔线的 PDF literal -- @param x (number) X 坐标 (sp) -- @param y (number) Y 坐标 (sp) -- @param width (number) 宽度 (sp) -- @param thickness (number) 线宽 (sp) -- @param color_str (string) RGB 颜色字符串 -- @return (string) PDF literal 字符串 local function create_divider_literal(x, y, width, thickness, color_str) local x_bp = x * sp_to_bp local y_bp = y * sp_to_bp local width_bp = width * sp_to_bp local thickness_bp = thickness * sp_to_bp return string.format( "q %.2f w %s RG %.4f %.4f m %.4f %.4f l S Q", thickness_bp, color_str, x_bp, y_bp, x_bp + width_bp, y_bp ) end -- ============================================================================ -- Node Creation Functions (创建节点) -- ============================================================================ --- 创建 PDF literal 节点 -- @param literal_str (string) PDF literal 字符串 -- @return (node) PDF literal 节点 (direct node) local function create_literal_node(literal_str) local n = node.new("whatsit", "pdf_literal") n.data = literal_str n.mode = 0 return D.todirect(n) end -- ============================================================================ -- Draw Banxin Internals (版心内部绘制) -- ============================================================================ --- 绘制版心分隔线 -- @param x (number) X 坐标 (sp) -- @param y (number) Y 坐标 (sp, 顶边缘) -- @param width (number) 宽度 (sp) -- @param upper_height (number) 上区域高度 (sp) -- @param middle_height (number) 中区域高度 (sp) -- @param thickness (number) 线宽 (sp) -- @param color_str (string) RGB 颜色字符串 -- @return (table) PDF literal 字符串数组 local function draw_dividers(x, y, width, upper_height, middle_height, thickness, color_str) local div1_y = y - upper_height local div2_y = div1_y - middle_height return { create_divider_literal(x, div1_y, width, thickness, color_str), create_divider_literal(x, div2_y, width, thickness, color_str), } end --- 绘制上鱼尾 -- @param x (number) X 坐标 (sp) -- @param div1_y (number) 第一分隔线 Y 坐标 (sp) -- @param width (number) 宽度 (sp) -- @param yuwei_dims (table) 鱼尾尺寸 -- @param color_str (string) RGB 颜色字符串 -- @param thickness (number) 线宽 (sp) -- @return (string) PDF literal 字符串 local function draw_upper_yuwei(x, div1_y, width, yuwei_dims, color_str, thickness) local yuwei_y = div1_y - yuwei_dims.gap return yuwei.draw_yuwei({ x = x, y = yuwei_y, width = width, edge_height = yuwei_dims.edge_height, notch_height = yuwei_dims.notch_height, style = "black", direction = 1, -- Notch at bottom (上鱼尾) color_str = color_str, extra_line = true, border_thickness = thickness, }) end --- 绘制下鱼尾 -- @param x (number) X 坐标 (sp) -- @param div2_y (number) 第二分隔线 Y 坐标 (sp) -- @param width (number) 宽度 (sp) -- @param yuwei_dims (table) 鱼尾尺寸 -- @param color_str (string) RGB 颜色字符串 -- @param thickness (number) 线宽 (sp) -- @return (string) PDF literal 字符串 local function draw_lower_yuwei(x, div2_y, width, yuwei_dims, color_str, thickness) local yuwei_y = div2_y + yuwei_dims.notch_height + yuwei_dims.gap return yuwei.draw_yuwei({ x = x, y = yuwei_y, width = width, edge_height = yuwei_dims.edge_height, notch_height = yuwei_dims.notch_height, style = "black", direction = -1, -- Notch at top (下鱼尾) color_str = color_str, extra_line = true, border_thickness = thickness, }) end --- 绘制版心的分隔线和鱼尾 -- @param params (table) 绘制参数 -- @return (table) { literals: string[], upper_height: number } local function draw_banxin(params) local x = params.x or 0 local y = params.y or 0 local width = params.width or 0 local total_height = params.total_height or 0 local r1 = params.upper_ratio or 0.28 local r2 = params.middle_ratio or 0.56 local color_str = params.color_str or "0 0 0" local thickness = params.border_thickness or 26214 local upper_height = total_height * r1 local middle_height = total_height * r2 local literals = {} -- Draw dividers if params.banxin_divider ~= false then local divider_literals = draw_dividers(x, y, width, upper_height, middle_height, thickness, color_str) for _, lit in ipairs(divider_literals) do table.insert(literals, lit) end end -- Draw yuwei local yuwei_dims = calculate_yuwei_dimensions(width) local div1_y = y - upper_height local div2_y = div1_y - middle_height if params.upper_yuwei ~= false then table.insert(literals, draw_upper_yuwei(x, div1_y, width, yuwei_dims, color_str, thickness)) end local lower_yuwei_enabled = params.lower_yuwei if lower_yuwei_enabled == nil then lower_yuwei_enabled = true end if lower_yuwei_enabled then table.insert(literals, draw_lower_yuwei(x, div2_y, width, yuwei_dims, color_str, thickness)) end return { literals = literals, upper_height = upper_height, } end -- ============================================================================ -- Text Rendering Functions (文字渲染) -- ============================================================================ -- Forward declaration local create_vertical_text --- 创建竖向排列的文字链 -- 将字符按从上到下的顺序排列在单列中。 create_vertical_text = function(text, params) if not text or text == "" then return nil end -- Parse UTF-8 characters local chars = {} for char in text:gmatch("[%z\1-\127\194-\244][\128-\191]*") do table.insert(chars, char) end local num_chars = #chars if num_chars == 0 then return nil end local x = params.x or 0 local y_top = params.y_top or 0 local width = params.width or 0 local height = params.height or 0 local num_cells = params.num_cells or num_chars local v_align = params.v_align or "center" local h_align = params.h_align or "center" local font_id = params.font_id or font.current() local shift_y = params.shift_y or 0 local font_scale_factor = 1.0 local base_font_data = font.getfont(font_id) -- Handle font size if provided if params.font_size then local fs = constants.to_dimen(params.font_size) if fs and fs > 0 then local current_font_data = font.getfont(font_id) if current_font_data then font_scale_factor = fs / current_font_data.size local new_font_data = {} for k, v in pairs(current_font_data) do new_font_data[k] = v end new_font_data.size = fs font_id = font.define(new_font_data) end end elseif params.font_scale then font_scale_factor = params.font_scale local current_font_data = font.getfont(font_id) if current_font_data then local new_font_data = {} for k, v in pairs(current_font_data) do new_font_data[k] = v end new_font_data.size = math.floor(new_font_data.size * params.font_scale + 0.5) font_id = font.define(new_font_data) end end -- Calculate cell height local cell_height = height / num_cells local head = nil local tail = nil for i, char in ipairs(chars) do -- Create glyph node local glyph = node.new(node.id("glyph")) glyph.char = utf8.codepoint(char) glyph.font = font_id glyph.lang = 0 local glyph_direct = D.todirect(glyph) -- Fetch glyph dimensions local cp = utf8.codepoint(char) local gw = (base_font_data and base_font_data.size or (65536 * 10)) * font_scale_factor local gh = gw * 0.8 local gd = gw * 0.2 if base_font_data and base_font_data.characters and base_font_data.characters[cp] then local char_data = base_font_data.characters[cp] gw = (char_data.width or gw) * font_scale_factor gh = (char_data.height or gh) * font_scale_factor gd = (char_data.depth or gd) * font_scale_factor D.setfield(glyph_direct, "width", math.floor(gw + 0.5)) D.setfield(glyph_direct, "height", math.floor(gh + 0.5)) D.setfield(glyph_direct, "depth", math.floor(gd + 0.5)) end local row = i - 1 local cell_y = y_top - row * cell_height - shift_y -- Position the glyph using core utility local _, kern = text_position.position_glyph(glyph_direct, x, cell_y, { cell_width = width, cell_height = cell_height, h_align = h_align, v_align = v_align, g_width = math.floor(gw + 0.5), g_height = math.floor(gh + 0.5), g_depth = math.floor(gd + 0.5), }) if head == nil then head = glyph_direct tail = kern else D.setlink(tail, glyph_direct) tail = kern end -- Debug rects (only when document-level debug is enabled) if setting_stack.get("debug") then if utils and utils.draw_debug_rect then head = utils.draw_debug_rect(head, glyph_direct, x, cell_y, width, -cell_height, "0 0 1 RG") end end end return head end --- Generic function to render a text section -- Consolidates the common pattern used by upper, middle, lower sections -- @param p_head (node) Current list head -- @param layout (table|nil) Layout params with: text, x, y_top, width, height, v_align, h_align, font_size -- @return (node) Updated list head local function render_text_section(p_head, layout) if not layout or not layout.text or layout.text == "" then return p_head end local chain = create_vertical_text(layout.text, { x = layout.x, y_top = layout.y_top, width = layout.width, height = layout.height, num_cells = layout.num_cells, v_align = layout.v_align or "center", h_align = layout.h_align or "center", font_size = layout.font_size, font_scale = layout.font_scale, font_id = layout.font_id, }) if chain then p_head = prepend_chain(p_head, chain) end return p_head end -- Reuse parse_section_text from banxin-layout module local parse_section_text = banxin_layout.parse_section_text -- ============================================================================ -- Debug Functions (调试功能) -- ============================================================================ --- 渲染调试矩形 -- @param p_head (node) 当前链表头 -- @param x, y, width, height (number) 矩形位置和尺寸 (sp) -- @return (node) 新的链表头 local function render_debug_rects(p_head, x, y, width, height) if not setting_stack.get("debug") then return p_head end p_head = utils.draw_debug_rect(p_head, nil, x, y, width, -height, "0 1 0 RG [2 2] 0 d") p_head = utils.draw_debug_rect(p_head, nil, x, y, width, -height, "1 0 0 RG") return p_head end -- ============================================================================ -- Background & Font Color Rendering (背景色与字体颜色) -- ============================================================================ --- 渲染区域背景色 -- @param p_head (node) Node list head -- @param element (table) Layout element with bg_color, x, y_top, width, height -- @return (node) Updated node list head local function render_section_background(p_head, element) if not element.bg_color or element.bg_color == "" then return p_head end local height = element.height or 0 if height <= 0 then return p_head end local lit = create_fill_rect_literal( element.x, element.y_top, element.width, height, element.bg_color ) p_head = D.insert_before(p_head, p_head, create_literal_node(lit)) return p_head end --- 包裹字体颜色:在 glyph chain 前后插入颜色 push/pop -- @param p_head (node) Node list head -- @param element (table) Layout element with font_color -- @param render_fn (function) Function that renders text and returns updated p_head -- @return (node) Updated node list head local function render_with_font_color(p_head, element, render_fn) if not element.font_color or element.font_color == "" then return render_fn(p_head) end -- Push color state before text local color_push = string.format("q %s rg %s RG", element.font_color, element.font_color) p_head = D.insert_before(p_head, p_head, create_literal_node(color_push)) -- Render text p_head = render_fn(p_head) -- Pop color state after text p_head = D.insert_before(p_head, p_head, create_literal_node("Q")) return p_head end -- ============================================================================ -- Pre-Rendered Box Placement (预排版 Box 放置) -- ============================================================================ --- Place a pre-rendered box at the position specified by the layout element. --- Uses kern+shift positioning (same pattern as render_floating_box). --- The box is copied so it can be reused across multiple banxin columns/pages. -- @param p_head (node) Current node list head -- @param element (table) Layout element with x, y_top, width, height -- @param box (node) Pre-rendered box node (will be copied, not consumed) -- @return (node) Updated node list head --- Place a pre-rendered box at the position specified by the layout element. --- Uses kern+shift positioning (same pattern as render_floating_box). --- The box is copied so it can be reused across multiple banxin columns/pages. -- @param p_head (node) Current node list head -- @param element (table) Layout element with x, y_top, width, height -- @param box (node) Pre-rendered box node (will be copied, not consumed) -- @return (node) Updated node list head local function render_pre_rendered_box(p_head, element, box) local copied = node.copy_list(box) local curr = D.todirect(copied) local box_h = D.getfield(curr, "height") or 0 local box_w = D.getfield(curr, "width") or 0 -- The pre-rendered box contains TextBox pipeline output. -- Internal glyphs have xoffset/yoffset in TextBox's own coordinate system. -- We position the box at block_y_top (top of content area after border + padding). -- TextBox already has padding deducted from its height, so we use block_y_top -- (not y_top which is the centered text position for the legacy path). -- -- TeX shift: positive = move box downward -- Box reference point: at (left, baseline) where baseline = top - height local target_y = element.block_y_top or element.y_top D.setfield(curr, "shift", -target_y) local k_pre = D.new(constants.KERN) D.setfield(k_pre, "subtype", 1) D.setfield(k_pre, "kern", element.x) local k_post = D.new(constants.KERN) D.setfield(k_post, "subtype", 1) D.setfield(k_post, "kern", -(element.x + box_w)) -- Insert: q (save) → kern → box → kern → Q (restore) local q_push = create_literal_node("q") local q_pop = create_literal_node("Q") p_head = D.insert_before(p_head, p_head, q_push) D.insert_after(p_head, q_push, k_pre) D.insert_after(p_head, k_pre, curr) D.insert_after(p_head, curr, k_post) D.insert_after(p_head, k_post, q_pop) return p_head end -- ============================================================================ -- Layout-Based Rendering Functions (布局驱动渲染) -- ============================================================================ --- Render middle section from layout data with runtime content -- @param p_head (node) Node list head -- @param element (table) Middle section layout element -- @param middle_section_text (string) Runtime middle section content -- @return (node) Updated node list head local function render_middle_section_from_layout(p_head, element, middle_section_text) if not middle_section_text or middle_section_text == "" then return p_head end local parts = parse_section_text(middle_section_text) if #parts == 0 then return p_head end local n_cols = math.max(#parts, element.n_cols or 1) local col_width = element.width / n_cols for i, sub_text in ipairs(parts) do local c = i - 1 local sub_x = element.x + (n_cols - 1 - c) * col_width local col_h_align = element.h_align or "center" if n_cols > 1 then if i == 1 then col_h_align = "right" elseif i == #parts then col_h_align = "left" end end local num_chars = count_utf8_chars(sub_text) local total_h = num_chars * element.grid_height p_head = render_text_section(p_head, { text = sub_text, x = sub_x, y_top = element.y_top, width = col_width, height = total_h, v_align = "center", h_align = col_h_align, font_size = element.font_size, font_scale = element.font_scale, font_id = element.font_id, }) end return p_head end --- Render page number from layout data with runtime content -- @param p_head (node) Node list head -- @param element (table) Page number layout element -- @param page_number (number) Runtime page number -- @return (node) Updated node list head local function render_page_number_from_layout(p_head, element, page_number, explicit_page_number) if not page_number and not explicit_page_number then return p_head end -- Use explicit page number string if provided (digital mode), -- otherwise auto-convert numeric page number to Chinese numeral local page_str = explicit_page_number or utils.to_chinese_numeral(page_number) if page_str == "" then return p_head end local num_chars = count_utf8_chars(page_str) local container_height = element.grid_height * num_chars -- Recalculate y_top based on actual character count local page_y_top if element.v_align == "center" then local available_middle_h = element.middle_height - element.upper_yuwei_total - element.lower_yuwei_total local center_y = element.middle_y_bottom + element.lower_yuwei_total + available_middle_h / 2 page_y_top = center_y + container_height / 2 else page_y_top = element.middle_y_bottom + element.lower_yuwei_total + element.page_bottom_margin + container_height end p_head = render_text_section(p_head, { text = page_str, x = element.x, y_top = page_y_top, width = element.width, height = container_height, v_align = element.v_align, h_align = element.h_align, font_size = element.font_size, font_id = element.font_id, }) return p_head end --- Draw banxin column from pre-calculated layout -- This function uses layout data calculated in the layout stage -- and resolves runtime content (page number, middle section text) at render time. -- @param p_head (node) Node list head -- @param layout (table) Pre-calculated layout from banxin-layout module -- @param runtime (table) Runtime content { middle_section_text, page_number } -- @return (node) Updated node list head local function draw_from_layout(p_head, layout, runtime) local col = layout.column local decorations = layout.decorations local regions = layout.regions -- Resolve font_id from style_registry (banxin_style may specify a font name) local current_style_id = style_registry.current_id() local style_font_name = style_registry.get_font(current_style_id) local base_font_id = font.current() if style_font_name and style_font_name ~= "" then -- font.fonts name field starts with the font name followed by ":mode=..." etc. local prefix = style_font_name .. ":" for fid, fdata in ipairs(font.fonts) do if type(fdata) == "table" and fdata.name and fdata.name:sub(1, #prefix) == prefix then base_font_id = fid break end end end -- 1. Draw border if col.draw_border then local border_literal = create_border_literal( col.x, col.y, col.width, col.height, col.border_thickness, col.color_str ) p_head = D.insert_before(p_head, p_head, create_literal_node(border_literal)) end -- 2. Draw dividers if decorations.draw_dividers then local divider_literals = draw_dividers( col.x, col.y, col.width, regions.upper.height, regions.middle.height, col.border_thickness, col.color_str ) for _, lit in ipairs(divider_literals) do p_head = D.insert_before(p_head, p_head, create_literal_node(lit)) end end -- 3. Draw fish tails (yuwei) local div1_y = col.y - regions.upper.height local div2_y = div1_y - regions.middle.height if decorations.upper_yuwei then local upper_lit = draw_upper_yuwei( col.x, div1_y, col.width, decorations.yuwei_dims, col.color_str, col.border_thickness ) p_head = D.insert_before(p_head, p_head, create_literal_node(upper_lit)) end if decorations.lower_yuwei then local lower_lit = draw_lower_yuwei( col.x, div2_y, col.width, decorations.yuwei_dims, col.color_str, col.border_thickness ) p_head = D.insert_before(p_head, p_head, create_literal_node(lower_lit)) end -- 4. Render text elements for _, element in ipairs(layout.elements) do element.font_id = element.font_id or base_font_id -- Render background color if specified p_head = render_section_background(p_head, element) if element.type == "upper_section" then local pre_box = _G.banxin and _G.banxin.upper_section_box if pre_box then p_head = render_pre_rendered_box(p_head, element, pre_box) else p_head = render_with_font_color(p_head, element, function(h) return render_text_section(h, element) end) end elseif element.type == "middle_section" then local middle_box = _G.banxin and _G.banxin.middle_section_box if middle_box then p_head = render_pre_rendered_box(p_head, element, middle_box) else p_head = render_with_font_color(p_head, element, function(h) return render_middle_section_from_layout(h, element, runtime.middle_section_text) end) end elseif element.type == "page_number" then p_head = render_page_number_from_layout(p_head, element, runtime.page_number, runtime.explicit_page_number) elseif element.type == "lower_section" then local lower_box = _G.banxin and _G.banxin.lower_section_box if lower_box then p_head = render_pre_rendered_box(p_head, element, lower_box) else p_head = render_with_font_color(p_head, element, function(h) return render_text_section(h, element) end) end end end -- 5. Debug rectangles p_head = render_debug_rects(p_head, col.x, col.y, col.width, col.height) return p_head end -- ============================================================================ -- Module Export -- ============================================================================ local banxin = { draw_banxin = draw_banxin, draw_from_layout = draw_from_layout, -- Internal functions exported for testing _internal = { count_utf8_chars = count_utf8_chars, calculate_yuwei_dimensions = calculate_yuwei_dimensions, calculate_yuwei_total_height = calculate_yuwei_total_height, create_border_literal = create_border_literal, create_fill_rect_literal = create_fill_rect_literal, create_divider_literal = create_divider_literal, parse_section_text = parse_section_text, render_pre_rendered_box = render_pre_rendered_box, }, } package.loaded['banxin.luatex-cn-banxin-render-banxin'] = banxin return banxin