diff --git a/assets/NotoEmoji-GPS.ttf b/assets/NotoEmoji-GPS.ttf new file mode 100644 index 0000000..207709b Binary files /dev/null and b/assets/NotoEmoji-GPS.ttf differ diff --git a/src/components/Viewfinder.tsx b/src/components/Viewfinder.tsx index 08e8e7e..3820d78 100644 --- a/src/components/Viewfinder.tsx +++ b/src/components/Viewfinder.tsx @@ -93,6 +93,12 @@ export default function Viewfinder({ require('../../assets/CourierPrime-Regular.ttf'), Math.round(vw * 0.032) ); + // Monochrome Noto Emoji subset (📍 U+1F4CD, 📷 U+1F4F7) for the watermark + // icons — glyphs painted with the same color as the text next to them. + const iconFont = useFont( + require('../../assets/NotoEmoji-GPS.ttf'), + Math.round(vw * 0.032) + ); const adjustments = recipe.adjustments; // Camera captures apply exposure compensation as a hardware AE bias (see the @@ -478,7 +484,7 @@ export default function Viewfinder({ }; const renderGPSWatermark = () => { - if (!useGeotag || !gpsInfo || !customFont) return null; + if (!useGeotag || !gpsInfo || !customFont || !iconFont) return null; const locationName = gpsInfo.locality || 'STREET VIEW'; const d = new Date(gpsInfo.timestamp); @@ -489,11 +495,17 @@ export default function Viewfinder({ // readable; polaroid frames push it further up onto the white area edge. const yOffset = vh - (selectedFrame === 'polaroid' ? vh * 0.18 + 24 : 150); const xOffset = selectedFrame === 'polaroid' ? vw * 0.08 : 16; + const gap = (rect: { width?: number } | null) => + Math.round((rect?.width ?? Math.round(vw * 0.032)) + vw * 0.012); + const pinW = iconFont.measureText('📍'); + const camW = iconFont.measureText('📷'); return ( - - + + + + ); }; diff --git a/src/utils/exportEngine.ts b/src/utils/exportEngine.ts index 4211be0..6e9edba 100644 --- a/src/utils/exportEngine.ts +++ b/src/utils/exportEngine.ts @@ -236,18 +236,19 @@ export async function processAndExportPhoto( // 8. Render GPS Geotag Watermark if (useGeotag && gpsInfo) { const fontAsset = Asset.fromModule(require('../../assets/CourierPrime-Regular.ttf')); - if (!fontAsset.localUri) { - await fontAsset.downloadAsync(); - } - - const fontData = await FileSystem.readAsStringAsync(fontAsset.localUri!, { - encoding: FileSystem.EncodingType.Base64, - }); + const iconFontAsset = Asset.fromModule(require('../../assets/NotoEmoji-GPS.ttf')); + await Promise.all([fontAsset.downloadAsync(), iconFontAsset.downloadAsync()]); + const [fontData, iconFontData] = await Promise.all([ + FileSystem.readAsStringAsync(fontAsset.localUri!, { encoding: FileSystem.EncodingType.Base64 }), + FileSystem.readAsStringAsync(iconFontAsset.localUri!, { encoding: FileSystem.EncodingType.Base64 }), + ]); const typeface = Skia.Typeface.MakeFreeTypeFaceFromData(Skia.Data.fromBase64(fontData)); + const iconTypeface = Skia.Typeface.MakeFreeTypeFaceFromData(Skia.Data.fromBase64(iconFontData)); - if (typeface) { + if (typeface && iconTypeface) { const fontSize = Math.round(width * 0.032); // 3.2% of image width const font = Skia.Font(typeface, fontSize); + const iconFont = Skia.Font(iconTypeface, fontSize); const textPaint = Skia.Paint(); textPaint.setColor(Skia.Color('#f59e0b')); // Amber 500 @@ -258,9 +259,12 @@ export async function processAndExportPhoto( const yOffset = height - (frameId === 'polaroid' ? height * 0.15 : fontSize * 1.5); const xOffset = frameId === 'polaroid' ? width * 0.08 : fontSize * 1.2; + const gap = Math.round(fontSize * 1.2); // icon box (~1em) + spacing - canvas.drawText(`📍 ${locationName}`, xOffset, yOffset - fontSize * 1.4, textPaint, font); - canvas.drawText(timestampStr, xOffset, yOffset, textPaint, font); + canvas.drawText('📍', xOffset, yOffset - fontSize * 1.4, textPaint, iconFont); + canvas.drawText(locationName, xOffset + gap, yOffset - fontSize * 1.4, textPaint, font); + canvas.drawText('📷', xOffset, yOffset, textPaint, iconFont); + canvas.drawText(timestampStr, xOffset + gap, yOffset, textPaint, font); } }