@@ -36,16 +36,18 @@ import { getNumericCSSValue } from '@/utils/formatting'
3636import {
3737 allFramePresets ,
3838 defaultFramePreset ,
39+ FONT_OPTIONS ,
40+ loadGoogleFont ,
3941 type FramePreset ,
4042 type FrameStyle
4143} from ' @/utils/framePresets'
4244import { allQrCodePresets , defaultPreset , type Preset } from ' @/utils/qrCodePresets'
4345import {
4446 CUSTOM_LOADED_PRESET_KEYS ,
45- LAST_LOADED_LOCALLY_PRESET_KEY ,
46- LOADED_FROM_FILE_PRESET_KEY ,
4747 hasStoredQRConfig ,
4848 isLocalStorageEnabled ,
49+ LAST_LOADED_LOCALLY_PRESET_KEY ,
50+ LOADED_FROM_FILE_PRESET_KEY ,
4951 loadQRConfig ,
5052 saveQRConfig ,
5153 serializeQRConfig ,
@@ -378,6 +380,17 @@ const frameSettings = computed(() => ({
378380 position: frameTextPosition .value ,
379381 style: frameStyle .value
380382}))
383+
384+ function onFontFamilyChange(value : string ): Promise <void > {
385+ // value may be a label name (e.g. "Poppins" from CSV) or a full CSS value (e.g. "'Poppins', sans-serif" from UI)
386+ const font = FONT_OPTIONS .find ((f ) => f .value === value || f .label === value )
387+ const resolvedValue = font ? font .value : value
388+ frameStyle .value = { ... frameStyle .value , fontFamily: resolvedValue || undefined }
389+ if (font ?.googleFontName ) {
390+ return loadGoogleFont (font .googleFontName )
391+ }
392+ return Promise .resolve ()
393+ }
381394// #endregion
382395
383396// #region /* Frame text autofill */ Fill if empty */
@@ -539,7 +552,7 @@ function downloadQRImage(format: 'png' | 'svg' | 'jpg') {
539552 el ,
540553 formatConfig .filename ,
541554 { ... getExportDimensions (), ... formatConfig .extraOptions },
542- styledBorderRadiusFormatted .value
555+ exportBorderRadius .value
543556 )
544557 } else {
545558 generateBatchQRCodes (format )
@@ -587,6 +600,14 @@ function applyQRConfig(config: QRCodeConfig, key?: string) {
587600 frameTextPosition .value = config .frame .position || ' bottom'
588601 frameStyle .value = { ... frameStyle .value , ... config .frame .style }
589602
603+ const restoredFontFamily = config .frame .style .fontFamily
604+ if (restoredFontFamily ) {
605+ const font = FONT_OPTIONS .find ((f ) => f .value === restoredFontFamily )
606+ if (font ?.googleFontName ) {
607+ loadGoogleFont (font .googleFontName )
608+ }
609+ }
610+
590611 const framePreset: FramePreset = {
591612 name: key || LAST_LOADED_LOCALLY_PRESET_KEY ,
592613 style: config .frame .style ,
@@ -672,6 +693,7 @@ const exportMode = ref(ExportMode.Single)
672693const dataStringsFromCsv = ref <string []>([])
673694const frameTextsFromCsv = ref <string []>([])
674695const fileNamesFromCsv = ref <string []>([])
696+ const fontFamiliesFromCsv = ref <string []>([])
675697
676698const inputFileForBatchEncoding = ref <File | null >(null )
677699const fileInput = ref <HTMLInputElement | null >(null )
@@ -709,6 +731,7 @@ const resetData = () => {
709731 dataStringsFromCsv .value = []
710732 frameTextsFromCsv .value = []
711733 fileNamesFromCsv .value = []
734+ fontFamiliesFromCsv .value = []
712735 isValidCsv .value = true
713736 resetBatchExportProgress ()
714737 isBatchExportSuccess .value = false
@@ -727,6 +750,10 @@ watch(previewRowIndex, (newIndex) => {
727750 ) {
728751 data .value = dataStringsFromCsv .value [newIndex ]
729752 frameText .value = frameTextsFromCsv .value [newIndex ] || defaultFrameText .value
753+ const fontFamily = fontFamiliesFromCsv .value [newIndex ]
754+ if (fontFamily !== undefined ) {
755+ onFontFamilyChange (fontFamily )
756+ }
730757 }
731758})
732759
@@ -778,6 +805,7 @@ const onBatchInputFileUpload = (event: Event) => {
778805 dataStringsFromCsv .value = batchResult .urls
779806 frameTextsFromCsv .value = batchResult .frameTexts
780807 fileNamesFromCsv .value = batchResult .fileNames
808+ fontFamiliesFromCsv .value = batchResult .fontFamilies
781809 showFrame .value = batchResult .hasCustomFrameText
782810 isValidCsv .value = true
783811 previewRowIndex .value = 0 // Reset preview to first row on new upload
@@ -786,6 +814,10 @@ const onBatchInputFileUpload = (event: Event) => {
786814 if (batchResult .urls .length > 0 ) {
787815 data .value = batchResult .urls [0 ]
788816 frameText .value = batchResult .frameTexts [0 ] || defaultFrameText .value
817+ const firstFontFamily = batchResult .fontFamilies [0 ]
818+ if (firstFontFamily ) {
819+ onFontFamilyChange (firstFontFamily )
820+ }
789821 }
790822 }
791823
@@ -837,22 +869,26 @@ async function generateBatchQRCodes(format: 'png' | 'svg' | 'jpg') {
837869 currentExportedQrCodeIndex .value = index
838870 const url = dataStringsFromCsv .value [index ]
839871 const currentFrameText = frameTextsFromCsv .value [index ]
872+ const currentFontFamily = fontFamiliesFromCsv .value [index ]
840873 data .value = url
841874 frameText .value = currentFrameText
875+ if (currentFontFamily !== undefined ) {
876+ await onFontFamilyChange (currentFontFamily )
877+ }
842878 await sleep (1000 )
843879 let dataUrl: string = ' '
844880 if (format === ' png' ) {
845- dataUrl = await getPngElement (el , getExportDimensions (), styledBorderRadiusFormatted .value )
881+ dataUrl = await getPngElement (el , getExportDimensions (), exportBorderRadius .value )
846882 } else if (format === ' jpg' ) {
847883 const jpgBgcolor =
848884 styleBackground .value === ' transparent' ? ' #ffffff' : styleBackground .value
849885 dataUrl = await getJpgElement (
850886 el ,
851887 { ... getExportDimensions (), bgcolor: jpgBgcolor },
852- styledBorderRadiusFormatted .value
888+ exportBorderRadius .value
853889 )
854890 } else {
855- dataUrl = await getSvgString (el , getExportDimensions (), styledBorderRadiusFormatted .value )
891+ dataUrl = await getSvgString (el , getExportDimensions (), exportBorderRadius .value )
856892 }
857893 createZipFile (zip , dataUrl , index , format )
858894 numQrCodesCreated ++
@@ -1425,6 +1461,26 @@ const updateDataFromModal = (newData: string) => {
14251461 placeholder =" 16px"
14261462 />
14271463 </div >
1464+ <div class =" sm:col-span-2" >
1465+ <label for =" frame-font-family" class =" mb-1 block text-sm" >{{
1466+ t('Font family')
1467+ }}</label >
1468+ <select
1469+ id =" frame-font-family"
1470+ class =" w-full text-input"
1471+ :value =" frameStyle.fontFamily ?? ''"
1472+ @change =" onFontFamilyChange(($event.target as HTMLSelectElement).value)"
1473+ >
1474+ <option
1475+ v-for =" font in FONT_OPTIONS"
1476+ :key =" font.value"
1477+ :value =" font.value"
1478+ :style =" font.value ? { fontFamily: font.value } : {}"
1479+ >
1480+ {{ font.label }}
1481+ </option >
1482+ </select >
1483+ </div >
14281484 </div >
14291485 </div >
14301486 </template >
@@ -1626,6 +1682,17 @@ const updateDataFromModal = (newData: string) => {
16261682 {{ fileNamesFromCsv[previewRowIndex] }}
16271683 </code >
16281684 </div >
1685+ <div v-if =" fontFamiliesFromCsv[previewRowIndex]" >
1686+ <span
1687+ class =" text-xs font-medium text-gray-500 dark:text-gray-400"
1688+ >{{ $t('Font family') }}</span
1689+ >
1690+ <code
1691+ class =" rounded bg-white px-2 py-1 font-mono text-sm dark:bg-gray-900"
1692+ >
1693+ {{ fontFamiliesFromCsv[previewRowIndex] }}
1694+ </code >
1695+ </div >
16291696 </div >
16301697 </div >
16311698 <div class =" mt-2 flex items-center justify-between" >
0 commit comments