@@ -14,9 +14,10 @@ import { parseDatasetNumber, parseDatasetEnum } from "./_dataset.js";
1414/**
1515 * bytes ルールのオプション
1616 * @typedef {Object } BytesRuleOptions
17- * @property {number } [max] - 最大長(グラフェム数) 。未指定なら制限なし
17+ * @property {number } [max] - バイト数 。未指定なら制限なし
1818 * @property {"block"|"error" } [mode="block"] - 入力中に最大長を超えたときの挙動
1919 * @property {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932" } [unit="utf-8"] - サイズの単位(sjis系を使用する場合はfilterも必須)
20+ * @property {"\n"|"\r"|"\r\n" } [newline="\n"] - 改行の扱い(バイト数計算に影響あり)
2021 */
2122
2223/**
@@ -25,25 +26,28 @@ import { parseDatasetNumber, parseDatasetEnum } from "./_dataset.js";
2526 */
2627
2728/**
28- * グラフェム/UTF-16コード単位/UTF-32コード単位の長さを調べる
29+ * テキストのバイト数を調べる
2930 * @param {string } text
3031 * @param {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932" } unit
32+ * @param {"\n"|"\r"|"\r\n" } newline
3133 * @returns {number }
3234 */
33- const getTextBytesByUnit = function ( text , unit ) {
35+ const getTextBytesByUnit = function ( text , unit , newline ) {
3436 if ( text . length === 0 ) {
3537 return 0 ;
3638 }
39+
40+ const normalizedText = text . replace ( / \r ? \n / g, newline ) ;
41+
3742 if ( unit === "utf-8" ) {
38- return Mojix . toUTF8Array ( text ) . length ;
43+ return Mojix . toUTF8Array ( normalizedText ) . length ;
3944 } else if ( unit === "utf-16" ) {
40- return Mojix . toUTF16Array ( text ) . length * 2 ;
45+ return Mojix . toUTF16Array ( normalizedText ) . length * 2 ;
4146 } else if ( unit === "utf-32" ) {
42- return Mojix . toUTF32Array ( text ) . length * 4 ;
47+ return Mojix . toUTF32Array ( normalizedText ) . length * 4 ;
4348 } else if ( unit === "sjis" || unit === "cp932" ) {
44- return Mojix . encode ( text , "Shift_JIS" ) . length ;
49+ return Mojix . encode ( normalizedText , "Shift_JIS" ) . length ;
4550 } else {
46- // ここには来ない
4751 throw new Error ( `Invalid unit: ${ unit } ` ) ;
4852 }
4953} ;
@@ -53,9 +57,10 @@ const getTextBytesByUnit = function(text, unit) {
5357 * @param {string } text
5458 * @param {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932" } unit
5559 * @param {number } max
60+ * @param {"\n"|"\r"|"\r\n" } newline
5661 * @returns {string }
5762 */
58- const cutTextByUnit = function ( text , unit , max ) {
63+ const cutTextByUnit = function ( text , unit , max , newline ) {
5964 /**
6065 * グラフェムの配列
6166 * @type {Grapheme[] }
@@ -74,19 +79,10 @@ const cutTextByUnit = function(text, unit, max) {
7479 const outputGraphemeArray = [ ] ;
7580
7681 for ( let i = 0 ; i < graphemeArray . length ; i ++ ) {
77- const g = graphemeArray [ i ] ;
78-
7982 // 1グラフェムあたりの長さ
80- let byteCount = 0 ;
81- if ( unit === "utf-8" ) {
82- byteCount = Mojix . toUTF8Array ( Mojix . toStringFromMojiArray ( [ g ] ) ) . length ;
83- } else if ( unit === "utf-16" ) {
84- byteCount = Mojix . toUTF16Array ( Mojix . toStringFromMojiArray ( [ g ] ) ) . length * 2 ;
85- } else if ( unit === "utf-32" ) {
86- byteCount = Mojix . toUTF32Array ( Mojix . toStringFromMojiArray ( [ g ] ) ) . length * 4 ;
87- } else if ( unit === "sjis" || unit === "cp932" ) {
88- byteCount = Mojix . encode ( Mojix . toStringFromMojiArray ( [ g ] ) , "Shift_JIS" ) . length ;
89- }
83+ const g = graphemeArray [ i ] ;
84+ const gText = Mojix . toStringFromMojiArray ( [ g ] ) ;
85+ const byteCount = getTextBytesByUnit ( gText , unit , newline ) ;
9086
9187 if ( count + byteCount > max ) {
9288 // 空配列を渡すとNUL文字を返すため、空配列のときは空文字を返す
@@ -111,15 +107,16 @@ const cutTextByUnit = function(text, unit, max) {
111107 * @param {string } insertedText 追加するテキスト
112108 * @param {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932" } unit
113109 * @param {number } max
110+ * @param {"\n"|"\r"|"\r\n" } newline
114111 * @returns {string } 追加するテキストを切ったもの(切る必要がない場合は insertedText をそのまま返す)
115112 */
116- const cutBytes = function ( beforeText , insertedText , unit , max ) {
117- const beforeTextLen = getTextBytesByUnit ( beforeText , unit ) ;
113+ const cutBytes = function ( beforeText , insertedText , unit , max , newline ) {
114+ const beforeTextLen = getTextBytesByUnit ( beforeText , unit , newline ) ;
118115
119116 // すでに最大長を超えている場合は追加のテキストを全て切る
120117 if ( beforeTextLen >= max ) { return "" ; }
121118
122- const insertedTextLen = getTextBytesByUnit ( insertedText , unit ) ;
119+ const insertedTextLen = getTextBytesByUnit ( insertedText , unit , newline ) ;
123120 const totalLen = beforeTextLen + insertedTextLen ;
124121
125122 if ( totalLen <= max ) {
@@ -129,7 +126,7 @@ const cutBytes = function(beforeText, insertedText, unit, max) {
129126
130127 // 超える場合は追加のテキストを切る
131128 const allowedAddLen = max - beforeTextLen ;
132- return cutTextByUnit ( insertedText , unit , allowedAddLen ) ;
129+ return cutTextByUnit ( insertedText , unit , allowedAddLen , newline ) ;
133130} ;
134131
135132/**
@@ -138,48 +135,46 @@ const cutBytes = function(beforeText, insertedText, unit, max) {
138135 * @returns {import("../text-input-guard.js").Rule }
139136 */
140137export function bytes ( options = { } ) {
141- /** @type {BytesRuleOptions } */
142- const opt = {
143- max : typeof options . max === "number" ? options . max : undefined ,
144- mode : options . mode ?? "block" ,
145- unit : options . unit ?? "utf-8"
146- } ;
138+ const max = typeof options . max === "number" ? options . max : undefined ;
139+ const mode = options . mode ?? "block" ;
140+ const unit = options . unit ?? "utf-8" ;
141+ const newline = options . newline ?? "\n" ;
147142
148143 return {
149144 name : "bytes" ,
150145 targets : [ "input" , "textarea" ] ,
151146
152147 normalizeChar ( value , ctx ) {
153148 // block 以外は何もしない
154- if ( opt . mode !== "block" ) {
149+ if ( mode !== "block" ) {
155150 return value ;
156151 }
157152 // max 未指定なら制限なし
158- if ( typeof opt . max !== "number" ) {
153+ if ( typeof max !== "number" ) {
159154 return value ;
160155 }
161156
162- const cutText = cutBytes ( ctx . beforeText , value , opt . unit , opt . max ) ;
157+ const cutText = cutBytes ( ctx . beforeText , value , unit , max , newline ) ;
163158 return cutText ;
164159 } ,
165160
166161 validate ( value , ctx ) {
167162 // error 以外は何もしない
168- if ( opt . mode !== "error" ) {
163+ if ( mode !== "error" ) {
169164 return ;
170165 }
171166 // max 未指定なら制限なし
172- if ( typeof opt . max !== "number" ) {
167+ if ( typeof max !== "number" ) {
173168 return ;
174169 }
175170
176- const len = getTextBytesByUnit ( value , opt . unit ) ;
177- if ( len > opt . max ) {
171+ const len = getTextBytesByUnit ( value , unit , newline ) ;
172+ if ( len > max ) {
178173 ctx . pushError ( {
179174 code : "bytes.max_overflow" ,
180175 rule : "bytes" ,
181176 phase : "validate" ,
182- detail : { limit : opt . max , actual : len }
177+ detail : { limit : max , actual : len }
183178 } ) ;
184179 }
185180 }
@@ -196,6 +191,7 @@ export function bytes(options = {}) {
196191 * - data-tig-rules-bytes-max -> dataset.tigRulesBytesMax
197192 * - data-tig-rules-bytes-mode -> dataset.tigRulesBytesMode
198193 * - data-tig-rules-bytes-unit -> dataset.tigRulesBytesUnit
194+ * - data-tig-rules-bytes-newline -> dataset.tigRulesBytesNewline
199195 *
200196 * @param {DOMStringMap } dataset
201197 * @param {HTMLInputElement|HTMLTextAreaElement } _el
@@ -228,5 +224,13 @@ bytes.fromDataset = function fromDataset(dataset, _el) {
228224 options . unit = unit ;
229225 }
230226
227+ const newline = parseDatasetEnum (
228+ dataset . tigRulesBytesNewline ,
229+ [ "\n" , "\r" , "\r\n" ]
230+ ) ;
231+ if ( newline != null ) {
232+ options . newline = newline ;
233+ }
234+
231235 return bytes ( options ) ;
232236} ;
0 commit comments