@@ -10,10 +10,12 @@ import {
1010 buildBwipOptions ,
1111 getDisplaySize ,
1212 eanCheckDigit ,
13+ upceCheckDigit ,
1314 get1DBwipScale ,
1415 getEanUpcLayout ,
1516 type EanUpcType ,
1617} from "./bwipHelpers" ;
18+ import { objectRotation } from "../../registry/rotation" ;
1719import {
1820 QR_FO_Y_OFFSET_DOTS ,
1921 QR_FT_MODULE_OFFSET ,
@@ -151,9 +153,12 @@ export function BarcodeObject({
151153 // Force-off when the symbology has no HRI in ZPL (e.g. GS1 Databar) — the
152154 // canvas must match the print output even if a legacy saved object still
153155 // carries printInterpretation: true.
154- const printInterp =
156+ const rotation = objectRotation ( obj . props ) ;
157+ const isUpright = rotation === "N" ;
158+ const printInterpEnabled =
155159 ! ObjectRegistry [ obj . type ] ?. interpretationLocked &&
156160 ! ! ( obj . props as { printInterpretation ?: boolean } ) . printInterpretation ;
161+ const printInterp = isUpright && printInterpEnabled ;
157162 const moduleWidth =
158163 ( obj . props as { moduleWidth ?: number } ) . moduleWidth ?? 2 ;
159164 const textFontSize = Math . max ( dotsToPx ( moduleWidth * 10 , scale , dpmm ) , 6 ) ;
@@ -320,44 +325,14 @@ export function BarcodeObject({
320325 fill = "#000000"
321326 listening = { false }
322327 /> ,
323- // check digit — outside-right (like leading digit on left)
324- < Text
325- key = "dc"
326- x = { w + 2 }
327- y = { textY }
328- width = { ldW }
329- text = { allDigits [ 11 ] }
330- fontSize = { textFontSize }
331- fontFamily = "'Courier New', monospace"
332- align = "left"
333- wrap = "none"
334- fill = "#000000"
335- listening = { false }
336- /> ,
337328 ] ;
338329 } else if ( obj . type === "upce" ) {
339330 const digits6 = rawContent
340331 . replace ( / \D / g, "" )
341332 . slice ( 0 , 6 )
342333 . padEnd ( 6 , "0" ) ;
343334
344- // Expand UPC-E to 11-digit UPC-A to compute check digit
345- const vA = digits6 [ 0 ] ?? "0" ,
346- vB = digits6 [ 1 ] ?? "0" ,
347- vC = digits6 [ 2 ] ?? "0" ;
348- const vD = digits6 [ 3 ] ?? "0" ,
349- vE = digits6 [ 4 ] ?? "0" ,
350- vF = digits6 [ 5 ] ?? "0" ;
351- const fi = parseInt ( vF , 10 ) ;
352- let expanded11 : string ;
353- if ( fi <= 2 ) expanded11 = `0${ vA } ${ vB } ${ vF } 0000${ vC } ${ vD } ${ vE } ` ;
354- else if ( fi === 3 ) expanded11 = `0${ vA } ${ vB } ${ vC } 00000${ vD } ${ vE } ` ;
355- else if ( fi === 4 ) expanded11 = `0${ vA } ${ vB } ${ vC } ${ vD } 00000${ vE } ` ;
356- else expanded11 = `0${ vA } ${ vB } ${ vC } ${ vD } ${ vE } ${ vF } 0000` ;
357- let ckSum = 0 ;
358- for ( let i = 0 ; i < 11 ; i ++ )
359- ckSum += parseInt ( expanded11 [ i ] ?? "0" , 10 ) * ( i % 2 === 0 ? 3 : 1 ) ;
360- const checkDigit = String ( ( 10 - ( ckSum % 10 ) ) % 10 ) ;
335+ const checkDigit = upceCheckDigit ( digits6 ) ;
361336
362337 // UPC-E: 6 digits centered over the data area (modules 3–44 of 51)
363338 const { xLeft : xMid , halfWidth : midW } = layout ;
@@ -444,6 +419,11 @@ export function BarcodeObject({
444419
445420 // ── Other 1D: separate Konva Text below bars ──────────────────────────
446421 const showText = BARCODE_1D_TYPES . has ( obj . type ) && printInterp ;
422+ // Rotated 1D: text overlay rotated to match the barcode orientation.
423+ const showRotatedText =
424+ ! isUpright &&
425+ printInterpEnabled &&
426+ BARCODE_1D_TYPES . has ( obj . type ) ;
447427
448428 let displayText = rawContent ;
449429 if ( obj . type === "code39" ) {
@@ -543,6 +523,147 @@ export function BarcodeObject({
543523 ) ;
544524 }
545525
526+ // ── Rotated 1D: text overlay rotated alongside the bars ──────────────
527+ if ( showRotatedText ) {
528+ // Rotation math (Konva y-down, CW positive):
529+ // R (rot=90): local-x→screen-down, local-y→screen-left
530+ // B (rot=-90): local-x→screen-up, local-y→screen-right
531+ // I (rot=180): local-x→screen-left, local-y→screen-up
532+ //
533+ // Text "side" for 90°/270°: standard 1D text is below bars in upright,
534+ // so after 90°CW it's on the LEFT; after 270°CW on the RIGHT.
535+ // LOGMARS is mirrored (text above in upright → right for 90°, left for 270°).
536+ const isTextAbove = obj . type === "logmars" ;
537+ // x-anchor for R/B (shared by all text nodes for a given rotation)
538+ const sideX =
539+ rotation === "R"
540+ ? isTextAbove ? w + textGap + textFontSize : - textGap
541+ : isTextAbove ? - ( textGap + textFontSize ) : w + textGap ;
542+ const tRot = rotation === "R" ? 90 : rotation === "I" ? 180 : - 90 ;
543+
544+ // ── EAN/UPC: reproduce upright digit layout along the rotated axis ──
545+ let textElements : React . ReactNode ;
546+ if ( EAN_UPC_TYPES . has ( obj . type ) ) {
547+ const bwipSc = get1DBwipScale ( moduleWidth , scale , dpmm ) ;
548+ // For I: encoding runs horizontally (canvas.width); for R/B: vertically (canvas.height)
549+ const encDisplay = rotation === "I" ? w : h ;
550+ const encCanvas = rotation === "I" ? barcodeCanvas . width : barcodeCanvas . height ;
551+ const layout = getEanUpcLayout ( obj . type as EanUpcType , encDisplay , encCanvas , bwipSc ) ;
552+ const { xLeft, xRight, halfWidth : halfW } = layout ;
553+ const ldW = textFontSize * 1.2 ;
554+
555+ const tStyle = {
556+ fontSize : textFontSize ,
557+ fontFamily : "'Courier New', monospace" as const ,
558+ wrap : "none" as const ,
559+ fill : "#000000" ,
560+ listening : false ,
561+ } ;
562+
563+ // Position a text node at `encPos` from barcode start, spanning `size`.
564+ // For R: encPos → screen-y downward from top (start=top).
565+ // For B: encPos → screen-y upward from bottom (start=bottom), anchor = h - encPos.
566+ // For I: encPos → screen-x leftward from right (start=right), anchor-x = w - encPos.
567+ const node = ( key : string , encPos : number , size : number , text : string ) => {
568+ const tx = rotation === "I" ? w - encPos : sideX ;
569+ const ty = rotation === "R" ? encPos : rotation === "B" ? h - encPos : - textGap ;
570+ return < Text key = { key } x = { tx } y = { ty } rotation = { tRot } width = { Math . max ( size , 1 ) } text = { text } align = "center" { ...tStyle } /> ;
571+ } ;
572+
573+ // Single digit floated BEFORE barcode start (outside the quiet zone).
574+ const sysNode = ( key : string , text : string ) => {
575+ // R: above top (y=-ldW); B: below bottom (y=h+ldW); I: right of barcode (x=w+ldW).
576+ const tx = rotation === "I" ? w + ldW : sideX ;
577+ const ty = rotation === "R" ? - ldW : rotation === "B" ? h + ldW : - textGap ;
578+ return < Text key = { key } x = { tx } y = { ty } rotation = { tRot } width = { Math . max ( ldW , 1 ) } text = { text } align = "center" { ...tStyle } /> ;
579+ } ;
580+
581+ // Single digit floated AFTER barcode end (UPC-A/UPC-E check digit).
582+ const trailNode = ( key : string , text : string ) => {
583+ // R: below bottom (y≈encDisplay); B: above top (y≈h-encDisplay); I: left of x=0.
584+ const tx = rotation === "I" ? - ldW : sideX ;
585+ const ty = rotation === "R" ? encDisplay : rotation === "B" ? h - encDisplay : - textGap ;
586+ return < Text key = { key } x = { tx } y = { ty } rotation = { tRot } width = { Math . max ( ldW , 1 ) } text = { text } align = "left" { ...tStyle } /> ;
587+ } ;
588+
589+ if ( obj . type === "ean13" ) {
590+ const d12 = rawContent . replace ( / \D / g, "" ) . slice ( 0 , 12 ) . padEnd ( 12 , "0" ) ;
591+ const all13 = d12 + eanCheckDigit ( d12 , 1 , 3 ) ;
592+ textElements = [
593+ sysNode ( "sys" , all13 [ 0 ] ?? "" ) ,
594+ node ( "left" , xLeft , halfW , all13 . slice ( 1 , 7 ) ) ,
595+ node ( "right" , xRight , halfW , all13 . slice ( 7 , 13 ) ) ,
596+ ] ;
597+ } else if ( obj . type === "ean8" ) {
598+ const d7 = rawContent . replace ( / \D / g, "" ) . slice ( 0 , 7 ) . padEnd ( 7 , "0" ) ;
599+ const all8 = d7 + eanCheckDigit ( d7 , 3 , 1 ) ;
600+ textElements = [
601+ node ( "left" , xLeft , halfW , all8 . slice ( 0 , 4 ) ) ,
602+ node ( "right" , xRight , halfW , all8 . slice ( 4 , 8 ) ) ,
603+ ] ;
604+ } else if ( obj . type === "upca" ) {
605+ const d11 = rawContent . replace ( / \D / g, "" ) . slice ( 0 , 11 ) . padEnd ( 11 , "0" ) ;
606+ const all12 = d11 + eanCheckDigit ( d11 , 3 , 1 ) ;
607+ textElements = [
608+ sysNode ( "sys" , all12 [ 0 ] ?? "" ) ,
609+ node ( "left" , xLeft , halfW , all12 . slice ( 1 , 6 ) ) ,
610+ node ( "right" , xRight , halfW , all12 . slice ( 6 , 11 ) ) ,
611+ ] ;
612+ } else if ( obj . type === "upce" ) {
613+ const d6 = rawContent . replace ( / \D / g, "" ) . slice ( 0 , 6 ) . padEnd ( 6 , "0" ) ;
614+ const ck = upceCheckDigit ( d6 ) ;
615+ textElements = [
616+ sysNode ( "sys" , "0" ) ,
617+ node ( "mid" , xLeft , halfW , d6 ) ,
618+ trailNode ( "trail" , ck ) ,
619+ ] ;
620+ }
621+ } else {
622+ // ── Other 1D: single centered text string ──────────────────────────
623+ let txtX : number ;
624+ let txtY : number ;
625+ let txtWidth : number ;
626+
627+ if ( rotation === "R" ) {
628+ txtX = sideX ; txtY = 0 ; txtWidth = h ;
629+ } else if ( rotation === "I" ) {
630+ txtX = w ;
631+ txtY = isTextAbove ? h + textGap + textFontSize : - textGap ;
632+ txtWidth = w ;
633+ } else {
634+ txtX = sideX ; txtY = h ; txtWidth = h ;
635+ }
636+
637+ textElements = (
638+ < Text
639+ x = { txtX } y = { txtY } rotation = { tRot } width = { Math . max ( txtWidth , 1 ) }
640+ text = { displayText } fontSize = { textFontSize }
641+ fontFamily = "'Courier New', monospace"
642+ align = "center" wrap = "none" fill = "#000000" listening = { false }
643+ />
644+ ) ;
645+ }
646+
647+ return (
648+ < Group
649+ id = { obj . id } x = { x } y = { y } draggable
650+ onClick = { ( e ) => onSelect ( e . evt . shiftKey || e . evt . ctrlKey || e . evt . metaKey ) }
651+ onTap = { ( ) => onSelect ( false ) }
652+ onDragMove = { ( e ) => e . target . position ( snapPos ( e . target . x ( ) , e . target . y ( ) ) ) }
653+ onDragEnd = { handleDragEnd }
654+ >
655+ < KImage x = { 0 } y = { 0 } image = { barcodeCanvas }
656+ width = { Math . max ( w , 1 ) } height = { Math . max ( h , 1 ) }
657+ imageSmoothingEnabled = { false }
658+ stroke = { isSelected ? "#6366f1" : undefined }
659+ strokeWidth = { isSelected ? 2 : 0 }
660+ strokeScaleEnabled = { false }
661+ />
662+ { textElements }
663+ </ Group >
664+ ) ;
665+ }
666+
546667 return (
547668 < KImage
548669 id = { obj . id }
0 commit comments