diff --git a/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/pdf/PdfSignatureOptions.java b/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/pdf/PdfSignatureOptions.java index a133c18..fbad127 100644 --- a/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/pdf/PdfSignatureOptions.java +++ b/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/pdf/PdfSignatureOptions.java @@ -25,7 +25,8 @@ public record PdfSignatureOptions( Integer targetPageNumber, CpfDisplayMode cpfDisplayMode, Boolean includeDateTime, - String customExtraInfo + String customExtraInfo, + Double customSealScale ) { public PdfSignatureOptions { @@ -51,6 +52,21 @@ public record PdfSignatureOptions( if (includeDateTime == null) { includeDateTime = sealTheme.defaultIncludeDateTime(); } + if (customSealScale == null || customSealScale <= 0) { + customSealScale = 1.0; + } + } + + /** + * Construtor para compatibilidade com assinaturas anteriores a customSealScale. + */ + public PdfSignatureOptions(String certificateId, char[] pinOrPassword, boolean visibleSeal, + PdfSealTheme sealTheme, PdfSealPosition position, PdfSealPageChoice pageChoice, + String reason, String location, Float customX, Float customY, + Integer targetPageNumber, CpfDisplayMode cpfDisplayMode, Boolean includeDateTime, + String customExtraInfo) { + this(certificateId, pinOrPassword, visibleSeal, sealTheme, position, pageChoice, reason, location, + customX, customY, targetPageNumber, cpfDisplayMode, includeDateTime, customExtraInfo, 1.0); } /** @@ -61,7 +77,7 @@ public record PdfSignatureOptions( String reason, String location, Float customX, Float customY, Integer targetPageNumber, CpfDisplayMode cpfDisplayMode, Boolean includeDateTime) { this(certificateId, pinOrPassword, visibleSeal, sealTheme, position, pageChoice, reason, location, - customX, customY, targetPageNumber, cpfDisplayMode, includeDateTime, null); + customX, customY, targetPageNumber, cpfDisplayMode, includeDateTime, null, 1.0); } /** @@ -70,7 +86,7 @@ public record PdfSignatureOptions( public PdfSignatureOptions(String certificateId, char[] pinOrPassword, boolean visibleSeal, PdfSealTheme sealTheme, PdfSealPosition position, PdfSealPageChoice pageChoice, String reason, String location) { - this(certificateId, pinOrPassword, visibleSeal, sealTheme, position, pageChoice, reason, location, null, null, null, null, null, null); + this(certificateId, pinOrPassword, visibleSeal, sealTheme, position, pageChoice, reason, location, null, null, null, null, null, null, 1.0); } public PdfSignatureOptions(String certificateId, char[] pinOrPassword, boolean visibleSeal, @@ -143,6 +159,7 @@ public record PdfSignatureOptions( } private String customExtraInfo; + private Double customSealScale = 1.0; public Builder includeDateTime(boolean include) { this.includeDateTime = include; @@ -154,6 +171,11 @@ public record PdfSignatureOptions( return this; } + public Builder customSealScale(Double scale) { + this.customSealScale = scale; + return this; + } + public Builder customPosition(Float x, Float y, Integer pageNumber) { this.customX = x; this.customY = y; @@ -179,7 +201,8 @@ public record PdfSignatureOptions( targetPageNumber, cpfDisplayMode, includeDateTime, - customExtraInfo + customExtraInfo, + customSealScale ); } } diff --git a/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/pdf/PdfSigningService.java b/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/pdf/PdfSigningService.java index dcc2e01..7e3984a 100644 --- a/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/pdf/PdfSigningService.java +++ b/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/pdf/PdfSigningService.java @@ -235,8 +235,11 @@ public class PdfSigningService { PDImageXObject pdImage = PDImageXObject.createFromByteArray(doc, sealPng, "cybercore_seal"); - float sealWidth = theme.width() * 0.72f; - float sealHeight = theme.height() * 0.72f; + float scale = (options.customSealScale() != null && options.customSealScale() > 0) + ? options.customSealScale().floatValue() + : 1.0f; + float sealWidth = (theme.width() * 0.72f) * scale; + float sealHeight = (theme.height() * 0.72f) * scale; float margin = 20f; // Se coordenadas customizadas foram informadas (clique ou drag & drop na página) diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/PdfCertificateSignDialog.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/PdfCertificateSignDialog.java index 909e04a..c936f9c 100644 --- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/PdfCertificateSignDialog.java +++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/PdfCertificateSignDialog.java @@ -66,8 +66,28 @@ public final class PdfCertificateSignDialog { Float customY, Integer customPageNumber, String reason, - String location - ) {} + String location, + Double customSealScale + ) { + public SealConfig( + boolean visibleSeal, + PdfSealTheme sealTheme, + CpfDisplayMode cpfDisplayMode, + boolean includeDateTime, + String customExtraInfo, + PdfSignDialog.PositionOption positionOption, + PdfSignDialog.PageOption pageOption, + boolean isCustomPosition, + Float customX, + Float customY, + Integer customPageNumber, + String reason, + String location + ) { + this(visibleSeal, sealTheme, cpfDisplayMode, includeDateTime, customExtraInfo, positionOption, + pageOption, isCustomPosition, customX, customY, customPageNumber, reason, location, 1.0); + } + } private final Stage stage; private final CertificateManager certManager; @@ -429,7 +449,8 @@ public final class PdfCertificateSignDialog { .includeDateTime(sealConfig.includeDateTime()) .customExtraInfo(sealConfig.customExtraInfo()) .reason(sealConfig.reason()) - .location(sealConfig.location()); + .location(sealConfig.location()) + .customSealScale(sealConfig.customSealScale()); if (sealConfig.isCustomPosition() && sealConfig.customX() != null && sealConfig.customY() != null) { b.customPosition(sealConfig.customX(), sealConfig.customY(), sealConfig.customPageNumber()); diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/PdfSignDialog.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/PdfSignDialog.java index 98518ed..e075259 100644 --- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/PdfSignDialog.java +++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/PdfSignDialog.java @@ -46,6 +46,8 @@ import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Rectangle; import javafx.stage.FileChooser; import javafx.stage.Modality; import javafx.stage.Stage; @@ -127,6 +129,22 @@ public final class PdfSignDialog { private ComboBox pageCombo; private ImageView sealPreviewImageView; + // Controle de Escala / Redimensionamento do Selo (1.0 = 100%) + private double sealScale = 1.0; + private static final double MIN_SEAL_SCALE = 0.40; + private static final double MAX_SEAL_SCALE = 1.80; + private boolean isResizeModeActive = false; + + // Componentes do grupo do selo e alça interativa + private Pane sealGroupPane; + private Rectangle sealSelectionBorder; + private Circle resizeHandle; + private Button btnToggleResize; + private Button btnResetSealScale; + private Label lblSealScaleIndicator; + private double resizeStartSceneX; + private double resizeStartBaseWidth; + // Visualizador Interativo da Folha private ScrollPane sheetScroll; private StackPane pageSheetContainer; @@ -463,13 +481,34 @@ public final class PdfSignDialog { zoomBox.getChildren().addAll(btnZoomOut, lblZoomValue, btnZoomIn, btnZoomFit); + // Grupo de Tamanho do Selo + HBox resizeToolBox = new HBox(6); + resizeToolBox.setAlignment(Pos.CENTER_LEFT); + resizeToolBox.setStyle("-fx-background-color: -fx-bg-primary; -fx-border-color: -fx-border-light; -fx-border-radius: 6px; -fx-padding: 2px 6px;"); + + btnToggleResize = new Button("📏 Redimensionar"); + btnToggleResize.getStyleClass().addAll("btn-secondary", "btn-compact"); + btnToggleResize.setTooltip(new Tooltip("Ativar/desativar alça interativa para alterar o tamanho do selo")); + btnToggleResize.setOnAction(e -> toggleResizeMode()); + + lblSealScaleIndicator = new Label("100%"); + lblSealScaleIndicator.setStyle("-fx-font-size: 11px; -fx-font-weight: bold; -fx-min-width: 36px; -fx-alignment: center;"); + + btnResetSealScale = new Button("↺ 100%"); + btnResetSealScale.getStyleClass().addAll("btn-secondary", "btn-compact"); + btnResetSealScale.setTooltip(new Tooltip("Restaurar tamanho padrão original (100%)")); + btnResetSealScale.setVisible(false); + btnResetSealScale.setOnAction(e -> applySealScale(1.0)); + + resizeToolBox.getChildren().addAll(btnToggleResize, lblSealScaleIndicator, btnResetSealScale); + Region tbSpacer = new Region(); HBox.setHgrow(tbSpacer, Priority.ALWAYS); lblPositionFeedback = new Label("👉 Clique na folha para posicionar"); lblPositionFeedback.setStyle("-fx-font-size: 11.5px; -fx-text-fill: -fx-accent; -fx-font-weight: bold;"); - toolbar.getChildren().addAll(pageNavBox, zoomBox, tbSpacer, lblPositionFeedback); + toolbar.getChildren().addAll(pageNavBox, zoomBox, resizeToolBox, tbSpacer, lblPositionFeedback); // ScrollPane da Folha de Papel sheetScroll = new ScrollPane(); @@ -517,14 +556,43 @@ public final class PdfSignDialog { handleSheetClick(event); }); + // Grupo visual do selo composto pela imagem, borda de seleção e alça de quina + sealGroupPane = new Pane(); + sealGroupPane.setVisible(false); + sealStampOverlayView = new ImageView(); sealStampOverlayView.setPreserveRatio(true); sealStampOverlayView.setCursor(Cursor.MOVE); DropShadow sealShadow = new DropShadow(8, 0, 2, Color.rgb(2, 132, 199, 0.65)); sealStampOverlayView.setEffect(sealShadow); - sealStampOverlayView.setVisible(false); - // Arraste com ancoragem precisa no ponto exato do clique dentro do selo (sem salto e sem drift) + sealSelectionBorder = new Rectangle(); + sealSelectionBorder.setFill(Color.TRANSPARENT); + sealSelectionBorder.setStroke(Color.rgb(2, 132, 199)); + sealSelectionBorder.setStrokeWidth(1.8); + sealSelectionBorder.getStrokeDashArray().addAll(5.0, 4.0); + sealSelectionBorder.setVisible(false); + + resizeHandle = new Circle(8); + resizeHandle.setFill(Color.rgb(2, 132, 199)); + resizeHandle.setStroke(Color.WHITE); + resizeHandle.setStrokeWidth(2.0); + resizeHandle.setCursor(Cursor.SE_RESIZE); + DropShadow handleShadow = new DropShadow(4, 0, 1, Color.rgb(0, 0, 0, 0.35)); + resizeHandle.setEffect(handleShadow); + resizeHandle.setVisible(false); + Tooltip.install(resizeHandle, new Tooltip("Puxe a quina para redimensionar o selo proporcionalmente")); + + // Duplo clique no selo para ativar/desativar o modo de redimensionamento + sealStampOverlayView.setOnMouseClicked(event -> { + sealDragOccurred = false; + if (event.getClickCount() == 2) { + toggleResizeMode(); + } + event.consume(); + }); + + // Arraste com ancoragem precisa no ponto exato do clique dentro do selo sealStampOverlayView.setOnMousePressed(event -> { dragStartOffsetInSealX = event.getX(); dragStartOffsetInSealY = event.getY(); @@ -536,7 +604,6 @@ public final class PdfSignDialog { if (!chkVisibleSeal.isSelected() || selectedPdfBytes == null) return; sealDragOccurred = true; - // Converte coordenada da cena para coordenadas locais do painel da folha Point2D localInSheet = interactiveOverlayPane.sceneToLocal(event.getSceneX(), event.getSceneY()); double targetX = localInSheet.getX() - dragStartOffsetInSealX; double targetY = localInSheet.getY() - dragStartOffsetInSealY; @@ -544,17 +611,32 @@ public final class PdfSignDialog { event.consume(); }); - sealStampOverlayView.setOnMouseReleased(event -> { - // Garante que o evento de soltura do mouse não borbulhe para a folha + sealStampOverlayView.setOnMouseReleased(event -> event.consume()); + + // Eventos da Alça de Redimensionamento Interativa + resizeHandle.setOnMousePressed(event -> { + resizeStartSceneX = event.getSceneX(); + resizeStartBaseWidth = currentBaseSealWidth * zoomFactor * sealScale; event.consume(); }); - sealStampOverlayView.setOnMouseClicked(event -> { - sealDragOccurred = false; + resizeHandle.setOnMouseDragged(event -> { + if (!chkVisibleSeal.isSelected() || selectedPdfBytes == null) return; + double deltaX = event.getSceneX() - resizeStartSceneX; + double newWidth = resizeStartBaseWidth + deltaX; + double baseW = currentBaseSealWidth * zoomFactor; + if (baseW > 0) { + applySealScale(newWidth / baseW); + } event.consume(); }); - interactiveOverlayPane.getChildren().add(sealStampOverlayView); + resizeHandle.setOnMouseReleased(event -> event.consume()); + resizeHandle.setOnMouseClicked(event -> event.consume()); + + sealGroupPane.getChildren().addAll(sealStampOverlayView, sealSelectionBorder, resizeHandle); + interactiveOverlayPane.getChildren().add(sealGroupPane); + pageSheetContainer.getChildren().addAll(pageImageView, interactiveOverlayPane); sheetWrapper.getChildren().add(pageSheetContainer); sheetScroll.setContent(sheetWrapper); @@ -563,6 +645,41 @@ public final class PdfSignDialog { return column; } + private void toggleResizeMode() { + this.isResizeModeActive = !this.isResizeModeActive; + if (sealSelectionBorder != null) { + sealSelectionBorder.setVisible(isResizeModeActive); + } + if (resizeHandle != null) { + resizeHandle.setVisible(isResizeModeActive); + } + if (btnToggleResize != null) { + if (isResizeModeActive) { + btnToggleResize.setText("✓ Concluir"); + btnToggleResize.setStyle("-fx-background-color: #0284c7; -fx-text-fill: white; -fx-font-weight: bold;"); + lblPositionFeedback.setText("👉 Puxe a alça azul no canto do selo para ajustar o tamanho"); + } else { + btnToggleResize.setText("📏 Redimensionar"); + btnToggleResize.setStyle(""); + updateFeedbackLabel(); + } + } + } + + private void applySealScale(double newScale) { + this.sealScale = Math.max(MIN_SEAL_SCALE, Math.min(MAX_SEAL_SCALE, Math.round(newScale * 100.0) / 100.0)); + if (lblSealScaleIndicator != null) { + lblSealScaleIndicator.setText(String.format("%.0f%%", sealScale * 100)); + } + if (btnResetSealScale != null) { + btnResetSealScale.setVisible(Math.abs(sealScale - 1.0) > 0.02); + } + + updateContainerZoomDimensions(); + repositionSealByRelativeCoords(); + updateFeedbackLabel(); + } + private void adjustZoom(double delta) { setZoom(Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, this.zoomFactor + delta))); } @@ -592,8 +709,19 @@ public final class PdfSignDialog { pageImageView.setFitHeight(currentH); } if (sealStampOverlayView != null) { - sealStampOverlayView.setFitWidth(currentBaseSealWidth * zoomFactor); - sealStampOverlayView.setFitHeight(currentBaseSealHeight * zoomFactor); + double sealW = currentBaseSealWidth * zoomFactor * sealScale; + double sealH = currentBaseSealHeight * zoomFactor * sealScale; + sealStampOverlayView.setFitWidth(sealW); + sealStampOverlayView.setFitHeight(sealH); + + if (sealSelectionBorder != null) { + sealSelectionBorder.setWidth(sealW); + sealSelectionBorder.setHeight(sealH); + } + if (resizeHandle != null) { + resizeHandle.setCenterX(sealW); + resizeHandle.setCenterY(sealH); + } } } @@ -602,15 +730,17 @@ public final class PdfSignDialog { double sheetW = currentBaseSheetWidth * zoomFactor; double sheetH = currentBaseSheetHeight * zoomFactor; - double sealW = currentBaseSealWidth * zoomFactor; - double sealH = currentBaseSealHeight * zoomFactor; + double sealW = currentBaseSealWidth * zoomFactor * sealScale; + double sealH = currentBaseSealHeight * zoomFactor * sealScale; double posX = Math.max(0, Math.min(sealRelativeX * sheetW, sheetW - sealW)); double posY = Math.max(0, Math.min(sealRelativeY * sheetH, sheetH - sealH)); - sealStampOverlayView.setLayoutX(posX); - sealStampOverlayView.setLayoutY(posY); - sealStampOverlayView.setVisible(true); + if (sealGroupPane != null) { + sealGroupPane.setLayoutX(posX); + sealGroupPane.setLayoutY(posY); + sealGroupPane.setVisible(true); + } } // ========================================================================= @@ -653,7 +783,9 @@ public final class PdfSignDialog { renderCurrentPage(); if (chkVisibleSeal.isSelected()) { - sealStampOverlayView.setVisible(true); + if (sealGroupPane != null) { + sealGroupPane.setVisible(true); + } resetSealToCornerPosition(PositionOption.BOTTOM_RIGHT); } } catch (Exception ex) { @@ -751,38 +883,42 @@ public final class PdfSignDialog { // ========================================================================= private void handleSheetClick(MouseEvent event) { if (!chkVisibleSeal.isSelected() || selectedPdfBytes == null) return; - double sealW = currentBaseSealWidth * zoomFactor; - double sealH = currentBaseSealHeight * zoomFactor; + double sealW = currentBaseSealWidth * zoomFactor * sealScale; + double sealH = currentBaseSealHeight * zoomFactor * sealScale; updateSealPositionFromDrag(event.getX() - sealW / 2.0, event.getY() - sealH / 2.0); } private void updateSealPositionFromDrag(double newLayoutX, double newLayoutY) { double sheetW = currentBaseSheetWidth * zoomFactor; double sheetH = currentBaseSheetHeight * zoomFactor; - double sealW = currentBaseSealWidth * zoomFactor; - double sealH = currentBaseSealHeight * zoomFactor; + double sealW = currentBaseSealWidth * zoomFactor * sealScale; + double sealH = currentBaseSealHeight * zoomFactor * sealScale; double clampedX = Math.max(0, Math.min(newLayoutX, sheetW - sealW)); double clampedY = Math.max(0, Math.min(newLayoutY, sheetH - sealH)); - sealStampOverlayView.setLayoutX(clampedX); - sealStampOverlayView.setLayoutY(clampedY); - sealStampOverlayView.setVisible(true); + if (sealGroupPane != null) { + sealGroupPane.setLayoutX(clampedX); + sealGroupPane.setLayoutY(clampedY); + sealGroupPane.setVisible(true); + } this.sealRelativeX = (sheetW > 0) ? (clampedX / sheetW) : 0; this.sealRelativeY = (sheetH > 0) ? (clampedY / sheetH) : 0; PdfSealTheme theme = activeSealTheme != null ? activeSealTheme : PdfSealThemeRegistry.getDefaultTheme(); - float sealPdfW = (float) ((theme != null) ? theme.width() * 0.72 : 144.0); - float sealPdfH = (float) ((theme != null) ? theme.height() * 0.72 : 44.0); + float basePdfW = (float) ((theme != null) ? theme.width() * 0.72 : 144.0); + float basePdfH = (float) ((theme != null) ? theme.height() * 0.72 : 44.0); + float scaledPdfW = basePdfW * (float) sealScale; + float scaledPdfH = basePdfH * (float) sealScale; // Conversão direta e exata da escala visual na tela para os pontos do PDF double scale = (currentPagePdfWidth > 0) ? (sheetW / currentPagePdfWidth) : 1.0; float calculatedPdfX = (float) (clampedX / scale); - float calculatedPdfY = (float) (currentPagePdfHeight - (clampedY / scale) - sealPdfH); + float calculatedPdfY = (float) (currentPagePdfHeight - (clampedY / scale) - scaledPdfH); - this.customPdfX = Math.max(0, Math.min(calculatedPdfX, currentPagePdfWidth - sealPdfW)); - this.customPdfY = Math.max(0, Math.min(calculatedPdfY, currentPagePdfHeight - sealPdfH)); + this.customPdfX = Math.max(0, Math.min(calculatedPdfX, currentPagePdfWidth - scaledPdfW)); + this.customPdfY = Math.max(0, Math.min(calculatedPdfY, currentPagePdfHeight - scaledPdfH)); this.customPageNumber = currentPageIndex + 1; this.isCustomPositionActive = true; @@ -799,8 +935,10 @@ public final class PdfSignDialog { } PdfSealTheme theme = activeSealTheme != null ? activeSealTheme : PdfSealThemeRegistry.getDefaultTheme(); - float sealPdfW = (float) ((theme != null) ? theme.width() * 0.72 : 144.0); - float sealPdfH = (float) ((theme != null) ? theme.height() * 0.72 : 44.0); + float basePdfW = (float) ((theme != null) ? theme.width() * 0.72 : 144.0); + float basePdfH = (float) ((theme != null) ? theme.height() * 0.72 : 44.0); + float sealPdfW = basePdfW * (float) sealScale; + float sealPdfH = basePdfH * (float) sealScale; float margin = 20f; float targetPdfX; @@ -853,16 +991,22 @@ public final class PdfSignDialog { private void updateFeedbackLabel() { if (isCustomPositionActive && customPageNumber != null && customPdfX != null && customPdfY != null) { - lblPositionFeedback.setText(String.format("📍 Selo na Pág. %d [X: %.0f pt, Y: %.0f pt]", - customPageNumber, customPdfX, customPdfY)); + String scaleStr = (Math.abs(sealScale - 1.0) > 0.02) ? String.format(" | Escala: %.0f%%", sealScale * 100) : ""; + lblPositionFeedback.setText(String.format("📍 Selo na Pág. %d [X: %.0f pt, Y: %.0f pt]%s", + customPageNumber, customPdfX, customPdfY, scaleStr)); } } private void toggleVisibleSeal(boolean active) { positionCombo.setDisable(!active); pageCombo.setDisable(!active); + if (btnToggleResize != null) { + btnToggleResize.setDisable(!active); + } sealPreviewImageView.setOpacity(active ? 1.0 : 0.3); - sealStampOverlayView.setVisible(active && selectedPdfBytes != null); + if (sealGroupPane != null) { + sealGroupPane.setVisible(active && selectedPdfBytes != null); + } } private void updateSealPreview() { @@ -927,19 +1071,21 @@ public final class PdfSignDialog { customPdfY, customPageNumber, DEFAULT_REASON, - DEFAULT_LOCATION + DEFAULT_LOCATION, + sealScale ); String sealSummary; if (chkVisibleSeal.isSelected()) { String themeName = theme.name(); + String scaleInfo = (Math.abs(sealScale - 1.0) > 0.02) ? String.format(" [Escala: %.0f%%]", sealScale * 100) : ""; if (isCustomPositionActive && customPageNumber != null && customPdfX != null && customPdfY != null) { - sealSummary = String.format("Selo Estampado (%s) — Pág. %d [X: %.0f pt, Y: %.0f pt]", - themeName, customPageNumber, customPdfX, customPdfY); + sealSummary = String.format("Selo Estampado (%s)%s — Pág. %d [X: %.0f pt, Y: %.0f pt]", + themeName, scaleInfo, customPageNumber, customPdfX, customPdfY); } else { - sealSummary = String.format("Selo Estampado (%s) — %s (%s)", - themeName, (pos != null ? pos.label : "Canto Inferior Direito"), (pg != null ? pg.label : "Última Página")); + sealSummary = String.format("Selo Estampado (%s)%s — %s (%s)", + themeName, scaleInfo, (pos != null ? pos.label : "Canto Inferior Direito"), (pg != null ? pg.label : "Última Página")); } } else { sealSummary = "Sem Selo Visual (Assinatura PAdES Invisível)";