@@ -92,6 +92,24 @@ export function normalizeHanXinInput(input) {
9292 throw new TypeError ( 'Han Xin data must be a string, Uint8Array, typed-array view, or ArrayBuffer' ) ;
9393}
9494
95+ export function normalizeHanXinUnicodeInput ( input ) {
96+ if ( typeof input !== 'string' ) throw new TypeError ( 'Han Xin Unicode mode requires string input' ) ;
97+ if ( ! input . length ) throw new RangeError ( 'Han Xin data must not be empty' ) ;
98+ const units = [ ] ;
99+ for ( const character of input ) {
100+ const codePoint = character . codePointAt ( 0 ) ;
101+ if ( codePoint >= 0xd800 && codePoint <= 0xdfff ) {
102+ throw new RangeError ( `Han Xin Unicode mode cannot encode an unpaired surrogate U+${ codePoint . toString ( 16 ) . toUpperCase ( ) } ` ) ;
103+ }
104+ if ( codePoint < 0x80 ) units . push ( codePoint ) ;
105+ else if ( codePoint < 0x800 ) units . push ( 0xc0 | codePoint >>> 6 , 0x80 | codePoint & 0x3f ) ;
106+ else if ( codePoint < 0x10000 ) units . push ( 0xe0 | codePoint >>> 12 , 0x80 | codePoint >>> 6 & 0x3f , 0x80 | codePoint & 0x3f ) ;
107+ else units . push ( 0xf0 | codePoint >>> 18 , 0x80 | codePoint >>> 12 & 0x3f ,
108+ 0x80 | codePoint >>> 6 & 0x3f , 0x80 | codePoint & 0x3f ) ;
109+ }
110+ return { units, inputType : 'text' , encoding : 'UTF-8' } ;
111+ }
112+
95113const isDigit = ( value ) => value >= 0x30 && value <= 0x39 ;
96114const isUpper = ( value ) => value >= 0x41 && value <= 0x5a ;
97115const isLower = ( value ) => value >= 0x61 && value <= 0x7a ;
@@ -204,6 +222,75 @@ class BitBuffer {
204222 }
205223}
206224
225+ const unicodeCountWidth = ( count ) => count <= 7 ? 4 : count <= 63 ? 8 : count <= 511 ? 12 : count <= 4095 ? 16 : 20 ;
226+
227+ function appendUnicodeCount ( output , count ) {
228+ if ( count < 1 || count > 32767 ) throw new RangeError ( 'A Han Xin Unicode segment cannot exceed 32767 byte groups' ) ;
229+ if ( count <= 7 ) output . append ( count , 4 ) ;
230+ else if ( count <= 63 ) output . append ( 0x80 | count , 8 ) ;
231+ else if ( count <= 511 ) output . append ( 0xc00 | count , 12 ) ;
232+ else if ( count <= 4095 ) output . append ( 0xe000 | count , 16 ) ;
233+ else output . append ( 0xf0000 | count , 20 ) ;
234+ }
235+
236+ function compactUnicodeHanXin ( units , eci ) {
237+ if ( eci ) throw new RangeError ( 'Han Xin Unicode mode does not support an ECI header' ) ;
238+ const costs = Array ( units . length + 1 ) . fill ( Number . POSITIVE_INFINITY ) ;
239+ const choices = Array ( units . length ) . fill ( null ) ;
240+ costs [ units . length ] = 0 ;
241+
242+ for ( let start = units . length - 1 ; start >= 0 ; start -- ) {
243+ for ( let width = 1 ; width <= 4 ; width ++ ) {
244+ const minima = Array ( width ) . fill ( 0xff ) ;
245+ const maxima = Array ( width ) . fill ( 0 ) ;
246+ const maximumGroups = Math . min ( 32767 , Math . floor ( ( units . length - start ) / width ) ) ;
247+ for ( let count = 1 ; count <= maximumGroups ; count ++ ) {
248+ const groupStart = start + ( count - 1 ) * width ;
249+ for ( let column = 0 ; column < width ; column ++ ) {
250+ const value = units [ groupStart + column ] ;
251+ minima [ column ] = Math . min ( minima [ column ] , value ) ;
252+ maxima [ column ] = Math . max ( maxima [ column ] , value ) ;
253+ }
254+ const differenceWidths = minima . map ( ( minimum , column ) => {
255+ const difference = maxima [ column ] - minimum ;
256+ return difference ? Math . floor ( Math . log2 ( difference ) ) + 1 : 0 ;
257+ } ) ;
258+ const end = start + count * width ;
259+ const bitLength = 4 + unicodeCountWidth ( count ) + width * 12 +
260+ count * differenceWidths . reduce ( ( sum , value ) => sum + value , 0 ) ;
261+ const candidate = bitLength + costs [ end ] ;
262+ if ( candidate < costs [ start ] ) {
263+ costs [ start ] = candidate ;
264+ choices [ start ] = { width, count, end, minima : [ ...minima ] , differenceWidths } ;
265+ }
266+ }
267+ }
268+ }
269+
270+ const output = new BitBuffer ( ) ;
271+ const modes = Array ( units . length ) ;
272+ const segments = [ ] ;
273+ output . append ( 9 , 4 ) ;
274+ for ( let start = 0 ; start < units . length ; ) {
275+ const choice = choices [ start ] ;
276+ output . append ( choice . width , 4 ) ;
277+ appendUnicodeCount ( output , choice . count ) ;
278+ for ( const width of choice . differenceWidths ) output . append ( width , 4 ) ;
279+ for ( const minimum of choice . minima ) output . append ( minimum , 8 ) ;
280+ for ( let position = start ; position < choice . end ; position += choice . width ) {
281+ for ( let column = 0 ; column < choice . width ; column ++ ) {
282+ output . append ( units [ position + column ] - choice . minima [ column ] , choice . differenceWidths [ column ] ) ;
283+ }
284+ }
285+ modes . fill ( `u${ choice . width } ` , start , choice . end ) ;
286+ segments . push ( { mode : `u${ choice . width } ` , start, end : choice . end , length : choice . end - start ,
287+ byteWidth : choice . width , count : choice . count } ) ;
288+ start = choice . end ;
289+ }
290+ output . append ( 15 , 4 ) ;
291+ return { bits : output . bits , modes, segments } ;
292+ }
293+
207294const URI_A_TOKENS = [
208295 ...'abcdefghijklmnopqrstuvwxyz0123456789./-_~:@?#=+$&' ,
209296 'http://' , 'https://' , 'ftp://' , 'mailto:' , 'ldap://' , 'tel:' , 'urn:' , 'www.' ,
@@ -420,11 +507,12 @@ function segmentsFromModes(modes) {
420507 return result ;
421508}
422509
423- export function compactHanXin ( units , { eci = 0 , gs1 = false , uri = false } = { } ) {
510+ export function compactHanXin ( units , { eci = 0 , gs1 = false , uri = false , unicode = false } = { } ) {
424511 if ( ! Number . isInteger ( eci ) || eci < 0 || eci > 999999 ) throw new RangeError ( 'ECI must be an integer from 0 to 999999' ) ;
425- if ( gs1 && uri ) throw new RangeError ( 'Han Xin GS1 and URI modes cannot be combined' ) ;
512+ if ( [ gs1 , uri , unicode ] . filter ( Boolean ) . length > 1 ) throw new RangeError ( 'Han Xin GS1, URI, and Unicode modes cannot be combined' ) ;
426513 if ( gs1 ) return compactGs1HanXin ( units , eci ) ;
427514 if ( uri ) return compactUriHanXin ( units , eci ) ;
515+ if ( unicode ) return compactUnicodeHanXin ( units , eci ) ;
428516 const modes = selectHanXinModes ( units ) ;
429517 const segments = segmentsFromModes ( modes ) ;
430518 const output = new BitBuffer ( ) ;
0 commit comments