Module:Soroban
Overview
This module is used to render a Soroban without using svg images.
Parameters displayed in syntax
All parameters:
{{#invoke:Soroban|render | rods = 0,9,8,7,6,5,4,3,2,1,0,0 | dot-every = 3 | beam-color = black | beam-line-color = black | dot-color = white | size = 0.7 }}
local p = {}
local MAX_RODS = 31
local function trim(s)
return mw.text.trim(s or "")
end
local function split_csv(s)
local t = {}
for item in mw.text.gsplit(s or "", ",") do
item = trim(item)
if item ~= "" then
table.insert(t, tonumber(item) or 0)
end
end
return t
end
local function limit_rods(values, max_rods)
local limited = {}
for i = 1, math.min(#values, max_rods) do
table.insert(limited, values[i])
end
return limited
end
local function px(n)
return string.format("%.1fpx", n)
end
local function clamp(n, min, max)
n = tonumber(n) or min
if n < min then
return min
elseif n > max then
return max
end
return n
end
local function clean_color(value, default)
value = trim(value)
if value == "" then
return default
end
-- Simple safe CSS color values:
-- #ccc, #cccccc, gray, black, white, rgb(220,220,220)
if mw.ustring.match(value, "^[#%w%s%-%.%(%)%,]+$") then
return value
end
return default
end
local function yesno(value, default)
value = mw.ustring.lower(trim(value or ""))
if value == "" then
return default
end
if value == "no" or value == "false" or value == "0" or value == "off" then
return false
end
if value == "yes" or value == "true" or value == "1" or value == "on" then
return true
end
return default
end
local function has_dot(dots, index, dot_every)
if dots[index] == 1 then
return true
end
if dot_every and dot_every > 0 then
return ((index - 1) % dot_every) == 0
end
return false
end
local function add_rect(parent, class, x, y, w, h, css)
local node = parent:tag("div")
:addClass(class)
:css({
position = "absolute",
left = px(x),
top = px(y),
width = px(w),
height = px(h),
["box-sizing"] = "border-box"
})
if css then
node:css(css)
end
return node
end
local function add_hex_bead(parent, x, y, w, h, active, size, bead_color, inactive_bead_color, bead_border_color, bead_line_color)
local fill = active and bead_color or inactive_bead_color
local outer_x = x - w / 2
local outer_y = y - h / 2
local border = 1.2 * size
-- One solid bead shape.
-- This avoids white vertical seams between triangle pieces.
parent:tag("div")
:addClass("soroban-bead")
:css({
position = "absolute",
left = px(outer_x),
top = px(outer_y),
width = px(w),
height = px(h),
background = fill,
border = px(border) .. " solid " .. bead_border_color,
["box-sizing"] = "border-box",
["clip-path"] = "polygon(28% 0%, 72% 0%, 100% 50%, 72% 100%, 28% 100%, 0% 50%)",
["z-index"] = "4"
})
-- Middle line across bead, similar to pgf-soroban.
add_rect(
parent,
"soroban-bead-middle-line",
outer_x + 4 * size,
y - 0.5 * size,
w - 8 * size,
1 * size,
{
background = bead_line_color,
["z-index"] = "6",
opacity = "0.65"
}
)
end
local function add_rod(parent, x, y, h, rod_width, rod_color)
add_rect(
parent,
"soroban-rod",
x - rod_width / 2,
y,
rod_width,
h,
{
background = rod_color,
["z-index"] = "1"
}
)
end
local function add_unit_dot(parent, x, y, dot_size, size, dot_color, dot_border_color)
parent:tag("div")
:addClass("soroban-unit-dot")
:css({
position = "absolute",
left = px(x - dot_size / 2),
top = px(y - dot_size / 2),
width = px(dot_size),
height = px(dot_size),
["border-radius"] = "50%",
background = dot_color,
border = px(1 * size) .. " solid " .. dot_border_color,
["box-sizing"] = "border-box",
["z-index"] = "10"
})
end
local function add_soroban_frame(parent, width, soroban_top, soroban_height, frame_thick, frame_color)
-- Backing thin border
add_rect(
parent,
"soroban-frame-background",
0,
soroban_top,
width,
soroban_height,
{
border = px(1) .. " solid " .. frame_color,
background = "transparent",
["z-index"] = "0"
}
)
-- Top frame bar
add_rect(
parent,
"soroban-frame-top",
0,
soroban_top,
width,
frame_thick,
{
background = frame_color,
["z-index"] = "8"
}
)
-- Bottom frame bar
add_rect(
parent,
"soroban-frame-bottom",
0,
soroban_top + soroban_height - frame_thick,
width,
frame_thick,
{
background = frame_color,
["z-index"] = "8"
}
)
-- Left frame bar
add_rect(
parent,
"soroban-frame-left",
0,
soroban_top,
frame_thick,
soroban_height,
{
background = frame_color,
["z-index"] = "8"
}
)
-- Right frame bar
add_rect(
parent,
"soroban-frame-right",
width - frame_thick,
soroban_top,
frame_thick,
soroban_height,
{
background = frame_color,
["z-index"] = "8"
}
)
end
function p.render(frame)
local args = frame.args
local values = split_csv(args.rods or "")
local dots = split_csv(args.dots or "")
-- Default: 13 rods, like a common soroban.
if #values == 0 then
values = split_csv("0,0,0,0,0,0,0,0,0,0,0,0,0")
end
values = limit_rods(values, MAX_RODS)
dots = limit_rods(dots, MAX_RODS)
local header = args.header or ""
local footer = args.footer or ""
local caption = args.caption or ""
local align = mw.ustring.lower(trim(args.align or ""))
if align ~= "right" and align ~= "left" and align ~= "center" then
align = ""
end
local size = clamp(args.size or 1, 0.25, 4)
local dot_every = tonumber(args["dot-every"] or "")
if dot_every then
dot_every = math.floor(dot_every)
if dot_every < 1 then
dot_every = nil
end
end
-- Default colors matched to the reference image.
local bead_color = clean_color(args["bead-color"], "#dedede")
local inactive_bead_color = clean_color(args["inactive-bead-color"], "#dedede")
local bead_border_color = clean_color(args["bead-border-color"], "#b3b3b3")
local bead_line_color = clean_color(args["bead-line-color"], "#8f8f8f")
local rod_color = clean_color(args["rod-color"], "#b3b3b3")
local frame_color = clean_color(args["frame-color"], "#9f9f9f")
local beam_color = clean_color(args["beam-color"], "#ffffff")
local beam_line_color = clean_color(args["beam-line-color"], "#000000")
local dot_color = clean_color(args["dot-color"], "#ffffff")
local dot_border_color = clean_color(args["dot-border-color"], "#999999")
local text_color = clean_color(args["text-color"], "inherit")
-- New option:
-- | frame = yes shows the frame
-- | frame = no removes the frame
local show_frame = yesno(args.frame, true)
local rod_count = #values
-- Main proportions.
local soroban_height = 231 * size
local rod_gap = 56.5 * size
local margin = 45 * size
local frame_thick = 10 * size
local header_height = header ~= "" and 28 * size or 0
local footer_height = footer ~= "" and 28 * size or 0
local caption_height = caption ~= "" and 32 * size or 0
local width = margin * 2 + rod_gap * (rod_count - 1)
local drawing_height = header_height + soroban_height + footer_height
local soroban_top = header_height
local beam_y = soroban_top + 66 * size
local rod_top = soroban_top + frame_thick
local rod_height = soroban_height - frame_thick * 2
local rod_width = 10 * size
local bead_width = 50 * size
local bead_height = 30 * size
local bead_step = 30 * size
local upper_inactive_y = soroban_top + 31 * size
local upper_active_y = beam_y - 22 * size
local lower_active_start_y = beam_y + 22 * size
local lower_inactive_start_y = soroban_top + 114 * size
local dot_size = 7 * size
local outer = mw.html.create("div")
outer:addClass("soroban-container")
:css({
width = px(width),
["max-width"] = "100%",
color = text_color,
["font-family"] = "sans-serif",
["box-sizing"] = "border-box"
})
if align == "right" then
outer:css({
float = "right",
clear = "right",
margin = "0 0 1em 1em"
})
elseif align == "left" then
outer:css({
float = "left",
clear = "left",
margin = "0 1em 1em 0"
})
elseif align == "center" then
outer:css({
margin = "0.5em auto"
})
else
outer:css({
margin = "0.5em 0"
})
end
local root = outer:tag("div")
:addClass("soroban-wrapper")
:css({
position = "relative",
width = px(width),
height = px(drawing_height),
["box-sizing"] = "border-box"
})
-- Header
if header ~= "" then
root:tag("div")
:addClass("soroban-header")
:wikitext(mw.text.nowiki(header))
:css({
position = "absolute",
left = "0",
top = "0",
width = "100%",
height = px(24 * size),
["line-height"] = px(24 * size),
["text-align"] = "center",
["font-size"] = px(14 * size),
color = text_color
})
end
-- Optional soroban frame
if show_frame then
add_soroban_frame(
root,
width,
soroban_top,
soroban_height,
frame_thick,
frame_color
)
end
-- Rods
for i, value in ipairs(values) do
local x = margin + (i - 1) * rod_gap
add_rod(
root,
x,
rod_top,
rod_height,
rod_width,
rod_color
)
end
-- Beam: white center with black upper and lower lines.
add_rect(
root,
"soroban-beam",
frame_thick,
beam_y - 5 * size,
width - frame_thick * 2,
10 * size,
{
background = beam_color,
["z-index"] = "2"
}
)
add_rect(
root,
"soroban-beam-top-line",
frame_thick,
beam_y - 5 * size,
width - frame_thick * 2,
1.2 * size,
{
background = beam_line_color,
["z-index"] = "3"
}
)
add_rect(
root,
"soroban-beam-bottom-line",
frame_thick,
beam_y + 4 * size,
width - frame_thick * 2,
1.2 * size,
{
background = beam_line_color,
["z-index"] = "3"
}
)
-- Beads and unit dots
for i, value in ipairs(values) do
value = tonumber(value) or 0
value = clamp(value, 0, 9)
local x = margin + (i - 1) * rod_gap
-- Upper bead
local upper_active = value >= 5
local upper_y = upper_active and upper_active_y or upper_inactive_y
add_hex_bead(
root,
x,
upper_y,
bead_width,
bead_height,
upper_active,
size,
bead_color,
inactive_bead_color,
bead_border_color,
bead_line_color
)
-- Lower beads
local lower_value = value % 5
for b = 1, 4 do
local active = b <= lower_value
local y
if active then
y = lower_active_start_y + (b - 1) * bead_step
else
y = lower_inactive_start_y + (b - 1) * bead_step
end
add_hex_bead(
root,
x,
y,
bead_width,
bead_height,
active,
size,
bead_color,
inactive_bead_color,
bead_border_color,
bead_line_color
)
end
if has_dot(dots, i, dot_every) then
add_unit_dot(
root,
x,
beam_y,
dot_size,
size,
dot_color,
dot_border_color
)
end
end
-- Footer
if footer ~= "" then
root:tag("div")
:addClass("soroban-footer")
:wikitext(mw.text.nowiki(footer))
:css({
position = "absolute",
left = "0",
bottom = "0",
width = "100%",
height = px(24 * size),
["line-height"] = px(24 * size),
["text-align"] = "center",
["font-size"] = px(12 * size),
color = text_color
})
end
-- Caption
if caption ~= "" then
outer:tag("div")
:addClass("soroban-caption")
:wikitext(mw.text.nowiki(caption))
:css({
width = "100%",
["box-sizing"] = "border-box",
["text-align"] = "center",
["font-size"] = px(12 * size),
["line-height"] = "1.35",
["padding-top"] = px(4 * size),
color = text_color
})
end
return tostring(outer)
end
return p
Content Disclaimer
Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.
- The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
- There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
- It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
- Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
- Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.