模組:QR
- 本模組的功能主要是串接Module:EncoderUtil中的QR碼編碼器({{QR}}),並實作資料格式適配器將輸入的字串以最適合QR碼的原始資料輸出,並提供原生QR碼的日文漢字轉換服務。本模組並無編碼或渲染QR碼的能力,只能用於產生QR碼的原始資料輸出。
- 完整的QR碼編碼功能位於Module:EncoderUtil
- 完整的QR碼渲染功能位於Module:RegularTiling({{模板樣式色塊圖}})
- 本系列的模組並不提供QR碼的解碼或掃描功能(沒可能用到,且LUA擴展也不能讀取圖片像素。)
函數說明
- _kanjiLen
- 將輸入的字串以原生QR碼的日文漢字規範之模式進行資料長度評估。
- p.logQR(QR碼物件)
- 在console中log一個QR碼。(請將CSS調整成
line-height: 12px;
來檢視) - p.textQR
- 以文字的形式印出一個wikitext的QR碼。然而其結果不是正方形的,也難保所有裝置的字寬、字高一致,故此法生成的QR碼可能無法掃描。
|
▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ |
- p._CreateQRcode(str, input_ec_level, input_mode, version_input, mask_input)
- 基於輸入的字串輸入一個QR碼原始資料
- 輸出的資料為一個二維陣列,1表示QR碼黑色區塊、0表示QR碼白色區塊、2表示QR碼function pattern的黑色區塊、-2表示QR碼function pattern的白色區塊
- p.checkMode(str,mode)
- 檢查輸入的字串是否能以特定的QR碼編碼模式表達
- p.getMode(str)
- 計算最適合輸入的字串的QR碼編碼模式
- _data_spilter(str)
- 將輸入的字串依照適合的模式分成多段
- _encodeQRdata(item, version)
- 將_data_spilter返回的分段物件轉為QR碼的二進制資料
- _length_binary(lenth,version,mode)
- 將字串長度資訊轉為QR碼的二進制資料
- p._kanjiQR(str,requested_ec_level)
- 完全以日文漢字模式生成QR碼
- p.unicode2shiftJISbytes(str)
- 將字串轉換為符合日文漢字模式的QR碼資料流
- p.checkJISX0208(str)
- 檢查輸入字串是否符合日文漢字模式
- p.JISX0208(str)
- 將輸入字串轉為符合日文漢字模式的字串(會直接刪除無法轉換的字元)
- p._byteJISX0208(str,offsetbyte)
- 將日文漢字字元轉換為符合日文漢字模式的QR碼字元值
參見
- Module:QR/kanji:UTF-8到JISX0208的轉換表,用於給QR碼編碼提供支援。
local p={}
local encode_table={}
local kanji_data = 'Module:QR/kanji'
local libencode = require('Module:EncoderUtil')
local yesno=require("Module:Yesno")
local libqrcode = libencode._get_libqrcode()
local lib_arg={}
local ec_level_list={l=1,m=2,q=3,h=4,[0]=1,[1]=1,[2]=2,[3]=3,[4]=4,
low=1,medium=2,quartile=3,high=4
}
local ec_level_table={'low','medium','quartile','high'}
local en_mode_list={n=1,a=2,b=4,k=8,j=8,[0]=0,[1]=1,[2]=2,[4]=4,[4]=8,
numeric=1,alphanumeric=2,byte=4,kanji=8,auto=0
}
local en_mode_table={'numeric','alphanumeric',[4]='byte',[8]='kanji'}
function _kanjiLen(str,mode)
if mode == 1 then -- numeric
return math.ceil(#str * 10 / 39)
elseif mode == 2 then -- alphanumeric
return math.ceil(#str * 11 / 26)
elseif mode == 4 then -- binary
return math.ceil(#str * 8 / 13)
else
return mw.ustring.len(str)
end
end
--line-height: 12px;
--p.logQR(({p._CreateQRcode("維基百科,自由的百科全書")})[2])
local text_QR_char_list={
[-2]='▁',--' '
[-1]='▁',
[0]='▁',
[1]='▇',
[2]='▇',
}
function p.logQR(qrTable)
local result = ''
for i=1,#qrTable do
result=result..'\n'
for j=1,#(qrTable[i]) do
result=result..text_QR_char_list[qrTable[j][i]]
end
end
mw.log(result)
end
function p.textQR(frame)
local args
if frame == mw.getCurrentFrame() then
-- We're being called via #invoke. The args are passed through to the module
-- from the template page, so use the args that were passed into the template.
if lib_arg.getArgs == nil then lib_arg = require('Module:Arguments') end
args = lib_arg.getArgs(frame, {parentFirst=true})
else
-- We're being called from another module or from the debug console, so assume
-- the args are passed in directly.
args = frame
end
local input_text=args.text or args.message or args.msg or args[1] or args['1']
local ec_level_list={l=1,m=2,q=3,h=4,[0]=1,[1]=1,[2]=2,[3]=3,[4]=4,
low=1,medium=2,quartile=3,high=4
}
local ec_level = ec_level_list[(args.eclevel or''):lower()]or 1
local input_pre_data = input_text
if yesno(args.bitstream or false) then input_pre_data = mw.text.split(input_text or '',',')end
local ok, qrTable = p._CreateQRcode(input_pre_data,ec_level,args.mode,tonumber(args.version or args.ver),tonumber(args.mask))
if not ok then return "" end
local result = ''
for i=0,#qrTable+1 do
if result ~= '' then result=result..'<br/>' end
result=result..'\n'
for j=0,#(qrTable[i]or qrTable[#qrTable])+1 do
result=result..text_QR_char_list[((qrTable[j] or {})[i]) or 0]
end
end
result=result..'<br/>\n'
return '<div style="line-height: 0.857em;">'..result..'</div>'
end
function p._CreateQRcode(str, input_ec_level, input_mode, version_input, mask_input)
local mode = en_mode_list[input_mode or 0]
local encode_mode, mode_flag = p.checkMode(str,mode)
local encode_len = libencode._get_string_length(str)
local encode_data = {{
mode=encode_mode,
text=str,
len=encode_len
}}
if encode_mode==8 then --kanji
encode_data, encode_len = _data_spilter(str)
encode_len = encode_len + #encode_data
end
local requested_ec_level = ec_level_list[input_ec_level or 0]
local version, ec_level = libqrcode.get_version_eclevel(encode_len,encode_mode,requested_ec_level)
local check_ver = tonumber(version_input or 0)
if check_ver ~= 0 and check_ver < version then mw.addWarning( string.format(
"Version %d 且容錯等級為 %s 的QR碼不足以容納 \"%s\" 。",check_ver,ec_level_table[requested_ec_level],str) ) end
if not mode_flag and mode~=nil and mode~=0 then mw.addWarning(string.format(
"無法將 \"%s\" 以編碼模式 %s 進行編碼。",str, en_mode_table[mode] or tostring(input_mode) )) end
if check_ver > version then version = check_ver end
local data_raw=''
for i=1,#encode_data do
local item = encode_data[i]
data_raw = data_raw .. _encodeQRdata(item,version)
end
data_raw = libqrcode.add_pad_data(version,ec_level,data_raw)
arranged_data = libqrcode.arrange_codewords_and_calculate_ec(version,ec_level,data_raw)
if math.fmod(#arranged_data,8) ~= 0 then
return false, string.format("Arranged data %% 8 != 0: data length = %d, mod 8 = %d",#arranged_data, math.fmod(#arranged_data,8))
end
arranged_data = arranged_data .. string.rep("0",libqrcode.remainder[version])
local tab
local mask = tonumber(mask_input) or -1
if mask>=0 and mask<8 then tab = libqrcode.get_matrix_and_penalty(version,ec_level,arranged_data,mask)
else tab = libqrcode.get_matrix_with_lowest_penalty(version,ec_level,arranged_data) end
return true, tab, ec_level, encode_mode, version
end
function p.checkMode(str,mode)
if str == mw.getCurrentFrame() then
-- We're being called via #invoke. The args are passed through to the module
-- from the template page, so use the args that were passed into the template.
if lib_arg.getArgs == nil then lib_arg = require('Module:Arguments') end
local args = lib_arg.getArgs(str, {parentFirst=true}) --frame
str = args[1] or args['1'] or args.str
mode = args[2] or args['2'] or args.mode
end
local calc_mode=p.getMode(str)
if mode == 1 then -- numeric
return calc_mode, calc_mode == mode
elseif mode == 2 then -- alphanumeric
if mode >= calc_mode then return mode, true end
return calc_mode, false
elseif mode == 4 or mode == 8 then -- binary or kanji
return mode, true
else --default
return calc_mode, false
end
end
function p.getMode(str)
if str == mw.getCurrentFrame() then
-- We're being called via #invoke. The args are passed through to the module
-- from the template page, so use the args that were passed into the template.
if lib_arg.getArgs == nil then lib_arg = require('Module:Arguments') end
local args = lib_arg.getArgs(str, {parentFirst=true}) --frame
str = args[1] or args['1'] or args.str
end
if type(str) ~= type("string") then --不能string:match的東西。
return 4
elseif mw.ustring.match(str,"^[0-9]+$") then
return 1
elseif mw.ustring.match(str,"^[0-9A-Z $%%*./:+-]+$") then
return 2
else
if p.checkJISX0208(str)then
return 8
else
return 4
end
end
end
function _data_spilter(str)
local total_len=0
local code_len=mw.ustring.len(str)
local spilt_result = {}
local buffer, str_mode, check_mode='',0,0
for i=1,code_len do
local unicode,char_it=mw.ustring.codepoint(str,i),mw.ustring.sub(str,i,i)
if not encode_table.shiftJIS then encode_table = mw.loadData(kanji_data)end
if encode_table.shiftJIS[unicode] then check_mode=8
elseif mw.ustring.match(char_it,'[0-9]+') then check_mode=1
elseif mw.ustring.match(char_it,'[0-9A-Z $%%*./:+-]+') then check_mode=2
else check_mode=4 end
if check_mode ~= str_mode then
if buffer~=''then
local buffer_len = _kanjiLen(buffer,str_mode)
total_len = total_len + buffer_len
spilt_result[#spilt_result + 1]={
mode=str_mode,
text=buffer,
len=buffer_len
}
end
buffer=''
end
str_mode = check_mode
buffer = buffer .. char_it
end
if buffer~=''then
local buffer_len = _kanjiLen(buffer,str_mode)
total_len = total_len + buffer_len
spilt_result[#spilt_result + 1]={
mode=str_mode,
text=buffer,
len=buffer_len
}
end
spilt_result[#spilt_result + 1]={mode=0,len=0}
return spilt_result, total_len
end
function p.check_str_mode(str,mode)
if str == mw.getCurrentFrame() then
-- We're being called via #invoke. The args are passed through to the module
-- from the template page, so use the args that were passed into the template.
if lib_arg.getArgs == nil then lib_arg = require('Module:Arguments') end
local args = lib_arg.getArgs(str, {parentFirst=true}) --frame
str = args[1] or args['1'] or args.str
mode = args[2] or args['2'] or args.mode
end
if string.match(str,"^[0-9]+$") then
return 1
elseif string.match(str,"^[0-9A-Z $%%*./:+-]+$") then
return 2
else
if p.checkJISX0208(str)then
return 8
else
return 4
end
end
end
function _length_binary(lenth,version,mode)
local i = mode
if mode == 4 then i = 3
elseif mode == 8 then i = 4
end
assert( i <= 4 )
local tab = { {10,9,8,8},{12,11,16,10},{14,13,16,12} }
local digits
if version < 10 then
digits = tab[1][i]
elseif version < 27 then
digits = tab[2][i]
elseif version <= 40 then
digits = tab[3][i]
else
assert(false, "get_length, version > 40 not supported")
end
return libencode._toBinary(lenth,digits)
end
function _encodeQRdata(item, version)
local data_raw=libencode._toBinary((type(item.text)==type(nil))and 0 or item.mode,4)
if type(item.text)~=type(nil) then
if item.mode==8 then
local input_bytes=p.unicode2shiftJISbytes(item.text)
data_raw = data_raw .. _length_binary(#input_bytes,version,item.mode)
for i=1,#input_bytes do data_raw = data_raw .. libencode._toBinary(input_bytes[i],13)end
else
data_raw = data_raw .. libqrcode.get_length((type(item.text)==type(nil))and '' or item.text, version, item.mode)
data_raw = data_raw .. libqrcode.encode_data(item.text,item.mode)
end
else
data_raw = data_raw .. libencode._toBinary(0,4)
end
return data_raw
end
function p._kanjiQR(str,requested_ec_level)
local version, ec_level
local input_bytes, local_mode=p.unicode2shiftJISbytes(str), 8
version, ec_level = libqrcode.get_version_eclevel(#input_bytes,8,requested_ec_level)
local length_string = libqrcode.get_length(input_bytes,version,local_mode)
local arranged_data, data_raw, mode, len_bitstring
version, ec_level, data_raw, mode, len_bitstring = version,ec_level,libencode._toBinary(local_mode,4),local_mode,length_string
data_raw = data_raw .. len_bitstring
for i=1,#input_bytes do data_raw = data_raw .. libencode._toBinary(input_bytes[i],13)end
data_raw = libqrcode.add_pad_data(version,ec_level,data_raw)
arranged_data = libqrcode.arrange_codewords_and_calculate_ec(version,ec_level,data_raw)
if math.fmod(#arranged_data,8) ~= 0 then
return false, string.format("Arranged data %% 8 != 0: data length = %d, mod 8 = %d",#arranged_data, math.fmod(#arranged_data,8))
end
arranged_data = arranged_data .. string.rep("0",libqrcode.remainder[version])
local tab = libqrcode.get_matrix_with_lowest_penalty(version,ec_level,arranged_data)
return true, tab
end
function p.unicode2shiftJISbytes(str)
if str == mw.getCurrentFrame() then
-- We're being called via #invoke. The args are passed through to the module
-- from the template page, so use the args that were passed into the template.
if lib_arg.getArgs == nil then lib_arg = require('Module:Arguments') end
local args = lib_arg.getArgs(str, {parentFirst=true}) --frame
str = args[1] or args['1'] or args.str
end
local code_len=mw.ustring.len(str)
local result={}
local flag=true
for i=1,code_len do
local codebyte=p._byteJISX0208(str,i)
local unicode=mw.ustring.codepoint(str,i)
if codebyte then result[#result+1]=codebyte
else
if (unicode>47 and unicode<58) or (unicode>64 and unicode<91) or (unicode>96 and unicode<123) then
codebyte = unicode + 159 + (unicode>96 and 1 or 0)
end
if codebyte then result[#result+1]=codebyte
else result[#result+1]=8 end
flag = false
--local unistr = '&#'..unicode..';'
--for j=1,#unistr do result[#result+1]=unistr:byte(j)end
end
end
return result
end
function p.checkJISX0208(str)
if str == mw.getCurrentFrame() then
-- We're being called via #invoke. The args are passed through to the module
-- from the template page, so use the args that were passed into the template.
if lib_arg.getArgs == nil then lib_arg = require('Module:Arguments') end
local args = lib_arg.getArgs(str, {parentFirst=true}) --frame
str = args[1] or args['1'] or args.str
end
local code_len=mw.ustring.len(str)
for i=1,code_len do
local unicode=mw.ustring.codepoint(str,i)
if not encode_table.shiftJIS then encode_table = mw.loadData(kanji_data)end
if not encode_table.shiftJIS[unicode] then return false end
end
return true
end
function p.JISX0208(str)
if str == mw.getCurrentFrame() then
-- We're being called via #invoke. The args are passed through to the module
-- from the template page, so use the args that were passed into the template.
if lib_arg.getArgs == nil then lib_arg = require('Module:Arguments') end
local args = lib_arg.getArgs(str, {parentFirst=true}) --frame
str = args[1] or args['1'] or args.str
end
local result=''
local code_len=mw.ustring.len(str)
for i=1,code_len do
local unicode=mw.ustring.codepoint(str,i)
if not encode_table.shiftJIS then encode_table = mw.loadData(kanji_data)end
if encode_table.shiftJIS[unicode] then result=result..mw.ustring.sub(str,i,i) end
end
return result
end
function p._byteJISX0208(str,offsetbyte)
if not encode_table.shiftJIS then encode_table = mw.loadData(kanji_data)end
local str_byte=mw.ustring.codepoint(str,offsetbyte or 1)
local shiftJIS_byte = encode_table.shiftJIS[str_byte]
local qr_byte=shiftJIS_byte
if shiftJIS_byte then
local bitwise=require('bit32')
local high_b=bitwise.rshift(bitwise.band(shiftJIS_byte,0xFF00),8)
local low_b=bitwise.band(shiftJIS_byte,0xFF)
local offset_data=((high_b>=0x81 and high_b<=0x9F) and 1 or((high_b>=0xE0 and high_b<=0xEB) and 2 or 0))
local offset_map = {0x8140,0xC140,[0]=0}
local byte_m = shiftJIS_byte - offset_map[offset_data]
local high_m=bitwise.rshift(bitwise.band(byte_m,0xFF00),8)
local low_m=bitwise.band(byte_m,0xFF)
qr_byte = (high_m * 0xC0) + low_m
end
return qr_byte
end
return p