feat: adicionar suporte a impressoras pequenas com parâmetro dpi e layout adaptativo para etiquetas

This commit is contained in:
2026-03-14 21:21:15 -03:00
parent b6e4aa7622
commit 5c8d6a368e
6 changed files with 261 additions and 64 deletions
+26 -7
View File
@@ -15,14 +15,18 @@ import { formatWeight } from '@shared/utils/filament';
import { Button } from '@presentation/components/ui/Button';
import { httpClient } from '@adapters/remote/httpClient';
type LabelSize = '50x30' | '62x29' | '38x25';
type LabelSize = '22x14' | '50x30' | '62x29' | '38x25';
const LABEL_SIZES: { id: LabelSize; label: string; sub: string }[] = [
{ id: '22x14', label: '22 × 14', sub: 'Niimbot D11/D110' },
{ id: '50x30', label: '50 × 30', sub: 'Padrão' },
{ id: '62x29', label: '62 × 29', sub: 'Brother DK' },
{ id: '38x25', label: '38 × 25', sub: 'Dymo 11354' },
];
// Conteúdo mínimo para etiquetas muito pequenas (Niimbot)
const MINI_LABEL_CONTENT: Set<ContentOption> = new Set(['color', 'name', 'qrcode']);
type ContentOption = 'color' | 'name' | 'material_brand' | 'net_weight' | 'print_temp' | 'qrcode';
const CONTENT_OPTIONS: { id: ContentOption; label: string }[] = [
@@ -64,6 +68,13 @@ export default function LabelScreen(): React.ReactElement {
const pct = calcFilamentPercentage(filament);
function handleSizeChange(size: LabelSize): void {
setSelectedSize(size);
if (size === '22x14') {
setEnabledContent(new Set(MINI_LABEL_CONTENT));
}
}
function toggleContent(opt: ContentOption): void {
setEnabledContent((prev) => {
const next = new Set(prev);
@@ -78,16 +89,24 @@ export default function LabelScreen(): React.ReactElement {
const height_mm = Number(heightStr);
const isPdf = selectedFormat === 'pdf';
const fields = Array.from(enabledContent).join(',');
// DPI alvo: 203 para Niimbot (impressora térmica de baixo custo), 96 para tela
const dpi = selectedSize === '22x14' ? 203 : undefined;
try {
if (isPdf) {
const response = await httpClient.get<ArrayBuffer>(
`/filaments/${id}/label.pdf`,
{ params: { width_mm, height_mm, fields }, responseType: 'arraybuffer' },
);
const base64 = btoa(
String.fromCharCode(...new Uint8Array(response.data)),
{ params: { width_mm, height_mm, fields, dpi }, responseType: 'arraybuffer' },
);
// Converte ArrayBuffer para Base64 em chunks para evitar stack overflow
// em buffers grandes (String.fromCharCode com spread falha acima de ~64KB)
const bytes = new Uint8Array(response.data);
let binary = '';
const CHUNK = 8192;
for (let i = 0; i < bytes.length; i += CHUNK) {
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
}
const base64 = btoa(binary);
const fileUri = `${FileSystem.cacheDirectory}meowspool-label-${id}.pdf`;
await FileSystem.writeAsStringAsync(fileUri, base64, {
encoding: FileSystem.EncodingType.Base64,
@@ -100,7 +119,7 @@ export default function LabelScreen(): React.ReactElement {
} else {
const response = await httpClient.get<string>(
`/filaments/${id}/label.svg`,
{ params: { width_mm, height_mm, fields }, responseType: 'text' },
{ params: { width_mm, height_mm, fields, dpi }, responseType: 'text' },
);
const fileUri = `${FileSystem.cacheDirectory}meowspool-label-${id}.svg`;
await FileSystem.writeAsStringAsync(fileUri, response.data, { encoding: 'utf8' });
@@ -180,7 +199,7 @@ export default function LabelScreen(): React.ReactElement {
{LABEL_SIZES.map((s) => (
<TouchableOpacity
key={s.id}
onPress={() => setSelectedSize(s.id)}
onPress={() => handleSizeChange(s.id)}
style={[styles.sizeChip, selectedSize === s.id && styles.sizeChipActive]}
>
<Text style={[styles.sizeChipMain, selectedSize === s.id && styles.sizeChipMainActive]}>