feat(pdf): suporte a redimensionamento interativo e proporcional do selo de assinatura

This commit is contained in:
Claudecio Martins
2026-10-11 10:30:49 -03:00
parent c997afbfd4
commit 4c30649598
4 changed files with 241 additions and 48 deletions
@@ -25,7 +25,8 @@ public record PdfSignatureOptions(
Integer targetPageNumber, Integer targetPageNumber,
CpfDisplayMode cpfDisplayMode, CpfDisplayMode cpfDisplayMode,
Boolean includeDateTime, Boolean includeDateTime,
String customExtraInfo String customExtraInfo,
Double customSealScale
) { ) {
public PdfSignatureOptions { public PdfSignatureOptions {
@@ -51,6 +52,21 @@ public record PdfSignatureOptions(
if (includeDateTime == null) { if (includeDateTime == null) {
includeDateTime = sealTheme.defaultIncludeDateTime(); 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, String reason, String location, Float customX, Float customY,
Integer targetPageNumber, CpfDisplayMode cpfDisplayMode, Boolean includeDateTime) { Integer targetPageNumber, CpfDisplayMode cpfDisplayMode, Boolean includeDateTime) {
this(certificateId, pinOrPassword, visibleSeal, sealTheme, position, pageChoice, reason, location, 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, public PdfSignatureOptions(String certificateId, char[] pinOrPassword, boolean visibleSeal,
PdfSealTheme sealTheme, PdfSealPosition position, PdfSealPageChoice pageChoice, PdfSealTheme sealTheme, PdfSealPosition position, PdfSealPageChoice pageChoice,
String reason, String location) { 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, public PdfSignatureOptions(String certificateId, char[] pinOrPassword, boolean visibleSeal,
@@ -143,6 +159,7 @@ public record PdfSignatureOptions(
} }
private String customExtraInfo; private String customExtraInfo;
private Double customSealScale = 1.0;
public Builder includeDateTime(boolean include) { public Builder includeDateTime(boolean include) {
this.includeDateTime = include; this.includeDateTime = include;
@@ -154,6 +171,11 @@ public record PdfSignatureOptions(
return this; return this;
} }
public Builder customSealScale(Double scale) {
this.customSealScale = scale;
return this;
}
public Builder customPosition(Float x, Float y, Integer pageNumber) { public Builder customPosition(Float x, Float y, Integer pageNumber) {
this.customX = x; this.customX = x;
this.customY = y; this.customY = y;
@@ -179,7 +201,8 @@ public record PdfSignatureOptions(
targetPageNumber, targetPageNumber,
cpfDisplayMode, cpfDisplayMode,
includeDateTime, includeDateTime,
customExtraInfo customExtraInfo,
customSealScale
); );
} }
} }
@@ -235,8 +235,11 @@ public class PdfSigningService {
PDImageXObject pdImage = PDImageXObject.createFromByteArray(doc, sealPng, "cybercore_seal"); PDImageXObject pdImage = PDImageXObject.createFromByteArray(doc, sealPng, "cybercore_seal");
float sealWidth = theme.width() * 0.72f; float scale = (options.customSealScale() != null && options.customSealScale() > 0)
float sealHeight = theme.height() * 0.72f; ? options.customSealScale().floatValue()
: 1.0f;
float sealWidth = (theme.width() * 0.72f) * scale;
float sealHeight = (theme.height() * 0.72f) * scale;
float margin = 20f; float margin = 20f;
// Se coordenadas customizadas foram informadas (clique ou drag & drop na página) // Se coordenadas customizadas foram informadas (clique ou drag & drop na página)
@@ -54,6 +54,22 @@ public final class PdfCertificateSignDialog {
private static final Logger logger = LoggerFactory.getLogger(PdfCertificateSignDialog.class); private static final Logger logger = LoggerFactory.getLogger(PdfCertificateSignDialog.class);
public record SealConfig( public record 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,
Double customSealScale
) {
public SealConfig(
boolean visibleSeal, boolean visibleSeal,
PdfSealTheme sealTheme, PdfSealTheme sealTheme,
CpfDisplayMode cpfDisplayMode, CpfDisplayMode cpfDisplayMode,
@@ -67,7 +83,11 @@ public final class PdfCertificateSignDialog {
Integer customPageNumber, Integer customPageNumber,
String reason, String reason,
String location String location
) {} ) {
this(visibleSeal, sealTheme, cpfDisplayMode, includeDateTime, customExtraInfo, positionOption,
pageOption, isCustomPosition, customX, customY, customPageNumber, reason, location, 1.0);
}
}
private final Stage stage; private final Stage stage;
private final CertificateManager certManager; private final CertificateManager certManager;
@@ -429,7 +449,8 @@ public final class PdfCertificateSignDialog {
.includeDateTime(sealConfig.includeDateTime()) .includeDateTime(sealConfig.includeDateTime())
.customExtraInfo(sealConfig.customExtraInfo()) .customExtraInfo(sealConfig.customExtraInfo())
.reason(sealConfig.reason()) .reason(sealConfig.reason())
.location(sealConfig.location()); .location(sealConfig.location())
.customSealScale(sealConfig.customSealScale());
if (sealConfig.isCustomPosition() && sealConfig.customX() != null && sealConfig.customY() != null) { if (sealConfig.isCustomPosition() && sealConfig.customX() != null && sealConfig.customY() != null) {
b.customPosition(sealConfig.customX(), sealConfig.customY(), sealConfig.customPageNumber()); b.customPosition(sealConfig.customX(), sealConfig.customY(), sealConfig.customPageNumber());
@@ -46,6 +46,8 @@ import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
@@ -127,6 +129,22 @@ public final class PdfSignDialog {
private ComboBox<PageOption> pageCombo; private ComboBox<PageOption> pageCombo;
private ImageView sealPreviewImageView; 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 // Visualizador Interativo da Folha
private ScrollPane sheetScroll; private ScrollPane sheetScroll;
private StackPane pageSheetContainer; private StackPane pageSheetContainer;
@@ -463,13 +481,34 @@ public final class PdfSignDialog {
zoomBox.getChildren().addAll(btnZoomOut, lblZoomValue, btnZoomIn, btnZoomFit); 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(); Region tbSpacer = new Region();
HBox.setHgrow(tbSpacer, Priority.ALWAYS); HBox.setHgrow(tbSpacer, Priority.ALWAYS);
lblPositionFeedback = new Label("👉 Clique na folha para posicionar"); lblPositionFeedback = new Label("👉 Clique na folha para posicionar");
lblPositionFeedback.setStyle("-fx-font-size: 11.5px; -fx-text-fill: -fx-accent; -fx-font-weight: bold;"); 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 // ScrollPane da Folha de Papel
sheetScroll = new ScrollPane(); sheetScroll = new ScrollPane();
@@ -517,14 +556,43 @@ public final class PdfSignDialog {
handleSheetClick(event); 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 = new ImageView();
sealStampOverlayView.setPreserveRatio(true); sealStampOverlayView.setPreserveRatio(true);
sealStampOverlayView.setCursor(Cursor.MOVE); sealStampOverlayView.setCursor(Cursor.MOVE);
DropShadow sealShadow = new DropShadow(8, 0, 2, Color.rgb(2, 132, 199, 0.65)); DropShadow sealShadow = new DropShadow(8, 0, 2, Color.rgb(2, 132, 199, 0.65));
sealStampOverlayView.setEffect(sealShadow); 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 -> { sealStampOverlayView.setOnMousePressed(event -> {
dragStartOffsetInSealX = event.getX(); dragStartOffsetInSealX = event.getX();
dragStartOffsetInSealY = event.getY(); dragStartOffsetInSealY = event.getY();
@@ -536,7 +604,6 @@ public final class PdfSignDialog {
if (!chkVisibleSeal.isSelected() || selectedPdfBytes == null) return; if (!chkVisibleSeal.isSelected() || selectedPdfBytes == null) return;
sealDragOccurred = true; sealDragOccurred = true;
// Converte coordenada da cena para coordenadas locais do painel da folha
Point2D localInSheet = interactiveOverlayPane.sceneToLocal(event.getSceneX(), event.getSceneY()); Point2D localInSheet = interactiveOverlayPane.sceneToLocal(event.getSceneX(), event.getSceneY());
double targetX = localInSheet.getX() - dragStartOffsetInSealX; double targetX = localInSheet.getX() - dragStartOffsetInSealX;
double targetY = localInSheet.getY() - dragStartOffsetInSealY; double targetY = localInSheet.getY() - dragStartOffsetInSealY;
@@ -544,17 +611,32 @@ public final class PdfSignDialog {
event.consume(); event.consume();
}); });
sealStampOverlayView.setOnMouseReleased(event -> { sealStampOverlayView.setOnMouseReleased(event -> event.consume());
// Garante que o evento de soltura do mouse não borbulhe para a folha
// Eventos da Alça de Redimensionamento Interativa
resizeHandle.setOnMousePressed(event -> {
resizeStartSceneX = event.getSceneX();
resizeStartBaseWidth = currentBaseSealWidth * zoomFactor * sealScale;
event.consume(); event.consume();
}); });
sealStampOverlayView.setOnMouseClicked(event -> { resizeHandle.setOnMouseDragged(event -> {
sealDragOccurred = false; 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(); 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); pageSheetContainer.getChildren().addAll(pageImageView, interactiveOverlayPane);
sheetWrapper.getChildren().add(pageSheetContainer); sheetWrapper.getChildren().add(pageSheetContainer);
sheetScroll.setContent(sheetWrapper); sheetScroll.setContent(sheetWrapper);
@@ -563,6 +645,41 @@ public final class PdfSignDialog {
return column; 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) { private void adjustZoom(double delta) {
setZoom(Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, this.zoomFactor + delta))); setZoom(Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, this.zoomFactor + delta)));
} }
@@ -592,8 +709,19 @@ public final class PdfSignDialog {
pageImageView.setFitHeight(currentH); pageImageView.setFitHeight(currentH);
} }
if (sealStampOverlayView != null) { if (sealStampOverlayView != null) {
sealStampOverlayView.setFitWidth(currentBaseSealWidth * zoomFactor); double sealW = currentBaseSealWidth * zoomFactor * sealScale;
sealStampOverlayView.setFitHeight(currentBaseSealHeight * zoomFactor); 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 sheetW = currentBaseSheetWidth * zoomFactor;
double sheetH = currentBaseSheetHeight * zoomFactor; double sheetH = currentBaseSheetHeight * zoomFactor;
double sealW = currentBaseSealWidth * zoomFactor; double sealW = currentBaseSealWidth * zoomFactor * sealScale;
double sealH = currentBaseSealHeight * zoomFactor; double sealH = currentBaseSealHeight * zoomFactor * sealScale;
double posX = Math.max(0, Math.min(sealRelativeX * sheetW, sheetW - sealW)); double posX = Math.max(0, Math.min(sealRelativeX * sheetW, sheetW - sealW));
double posY = Math.max(0, Math.min(sealRelativeY * sheetH, sheetH - sealH)); double posY = Math.max(0, Math.min(sealRelativeY * sheetH, sheetH - sealH));
sealStampOverlayView.setLayoutX(posX); if (sealGroupPane != null) {
sealStampOverlayView.setLayoutY(posY); sealGroupPane.setLayoutX(posX);
sealStampOverlayView.setVisible(true); sealGroupPane.setLayoutY(posY);
sealGroupPane.setVisible(true);
}
} }
// ========================================================================= // =========================================================================
@@ -653,7 +783,9 @@ public final class PdfSignDialog {
renderCurrentPage(); renderCurrentPage();
if (chkVisibleSeal.isSelected()) { if (chkVisibleSeal.isSelected()) {
sealStampOverlayView.setVisible(true); if (sealGroupPane != null) {
sealGroupPane.setVisible(true);
}
resetSealToCornerPosition(PositionOption.BOTTOM_RIGHT); resetSealToCornerPosition(PositionOption.BOTTOM_RIGHT);
} }
} catch (Exception ex) { } catch (Exception ex) {
@@ -751,38 +883,42 @@ public final class PdfSignDialog {
// ========================================================================= // =========================================================================
private void handleSheetClick(MouseEvent event) { private void handleSheetClick(MouseEvent event) {
if (!chkVisibleSeal.isSelected() || selectedPdfBytes == null) return; if (!chkVisibleSeal.isSelected() || selectedPdfBytes == null) return;
double sealW = currentBaseSealWidth * zoomFactor; double sealW = currentBaseSealWidth * zoomFactor * sealScale;
double sealH = currentBaseSealHeight * zoomFactor; double sealH = currentBaseSealHeight * zoomFactor * sealScale;
updateSealPositionFromDrag(event.getX() - sealW / 2.0, event.getY() - sealH / 2.0); updateSealPositionFromDrag(event.getX() - sealW / 2.0, event.getY() - sealH / 2.0);
} }
private void updateSealPositionFromDrag(double newLayoutX, double newLayoutY) { private void updateSealPositionFromDrag(double newLayoutX, double newLayoutY) {
double sheetW = currentBaseSheetWidth * zoomFactor; double sheetW = currentBaseSheetWidth * zoomFactor;
double sheetH = currentBaseSheetHeight * zoomFactor; double sheetH = currentBaseSheetHeight * zoomFactor;
double sealW = currentBaseSealWidth * zoomFactor; double sealW = currentBaseSealWidth * zoomFactor * sealScale;
double sealH = currentBaseSealHeight * zoomFactor; double sealH = currentBaseSealHeight * zoomFactor * sealScale;
double clampedX = Math.max(0, Math.min(newLayoutX, sheetW - sealW)); double clampedX = Math.max(0, Math.min(newLayoutX, sheetW - sealW));
double clampedY = Math.max(0, Math.min(newLayoutY, sheetH - sealH)); double clampedY = Math.max(0, Math.min(newLayoutY, sheetH - sealH));
sealStampOverlayView.setLayoutX(clampedX); if (sealGroupPane != null) {
sealStampOverlayView.setLayoutY(clampedY); sealGroupPane.setLayoutX(clampedX);
sealStampOverlayView.setVisible(true); sealGroupPane.setLayoutY(clampedY);
sealGroupPane.setVisible(true);
}
this.sealRelativeX = (sheetW > 0) ? (clampedX / sheetW) : 0; this.sealRelativeX = (sheetW > 0) ? (clampedX / sheetW) : 0;
this.sealRelativeY = (sheetH > 0) ? (clampedY / sheetH) : 0; this.sealRelativeY = (sheetH > 0) ? (clampedY / sheetH) : 0;
PdfSealTheme theme = activeSealTheme != null ? activeSealTheme : PdfSealThemeRegistry.getDefaultTheme(); PdfSealTheme theme = activeSealTheme != null ? activeSealTheme : PdfSealThemeRegistry.getDefaultTheme();
float sealPdfW = (float) ((theme != null) ? theme.width() * 0.72 : 144.0); float basePdfW = (float) ((theme != null) ? theme.width() * 0.72 : 144.0);
float sealPdfH = (float) ((theme != null) ? theme.height() * 0.72 : 44.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 // Conversão direta e exata da escala visual na tela para os pontos do PDF
double scale = (currentPagePdfWidth > 0) ? (sheetW / currentPagePdfWidth) : 1.0; double scale = (currentPagePdfWidth > 0) ? (sheetW / currentPagePdfWidth) : 1.0;
float calculatedPdfX = (float) (clampedX / scale); 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.customPdfX = Math.max(0, Math.min(calculatedPdfX, currentPagePdfWidth - scaledPdfW));
this.customPdfY = Math.max(0, Math.min(calculatedPdfY, currentPagePdfHeight - sealPdfH)); this.customPdfY = Math.max(0, Math.min(calculatedPdfY, currentPagePdfHeight - scaledPdfH));
this.customPageNumber = currentPageIndex + 1; this.customPageNumber = currentPageIndex + 1;
this.isCustomPositionActive = true; this.isCustomPositionActive = true;
@@ -799,8 +935,10 @@ public final class PdfSignDialog {
} }
PdfSealTheme theme = activeSealTheme != null ? activeSealTheme : PdfSealThemeRegistry.getDefaultTheme(); PdfSealTheme theme = activeSealTheme != null ? activeSealTheme : PdfSealThemeRegistry.getDefaultTheme();
float sealPdfW = (float) ((theme != null) ? theme.width() * 0.72 : 144.0); float basePdfW = (float) ((theme != null) ? theme.width() * 0.72 : 144.0);
float sealPdfH = (float) ((theme != null) ? theme.height() * 0.72 : 44.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 margin = 20f;
float targetPdfX; float targetPdfX;
@@ -853,16 +991,22 @@ public final class PdfSignDialog {
private void updateFeedbackLabel() { private void updateFeedbackLabel() {
if (isCustomPositionActive && customPageNumber != null && customPdfX != null && customPdfY != null) { if (isCustomPositionActive && customPageNumber != null && customPdfX != null && customPdfY != null) {
lblPositionFeedback.setText(String.format("📍 Selo na Pág. %d [X: %.0f pt, Y: %.0f pt]", String scaleStr = (Math.abs(sealScale - 1.0) > 0.02) ? String.format(" | Escala: %.0f%%", sealScale * 100) : "";
customPageNumber, customPdfX, customPdfY)); 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) { private void toggleVisibleSeal(boolean active) {
positionCombo.setDisable(!active); positionCombo.setDisable(!active);
pageCombo.setDisable(!active); pageCombo.setDisable(!active);
if (btnToggleResize != null) {
btnToggleResize.setDisable(!active);
}
sealPreviewImageView.setOpacity(active ? 1.0 : 0.3); sealPreviewImageView.setOpacity(active ? 1.0 : 0.3);
sealStampOverlayView.setVisible(active && selectedPdfBytes != null); if (sealGroupPane != null) {
sealGroupPane.setVisible(active && selectedPdfBytes != null);
}
} }
private void updateSealPreview() { private void updateSealPreview() {
@@ -927,19 +1071,21 @@ public final class PdfSignDialog {
customPdfY, customPdfY,
customPageNumber, customPageNumber,
DEFAULT_REASON, DEFAULT_REASON,
DEFAULT_LOCATION DEFAULT_LOCATION,
sealScale
); );
String sealSummary; String sealSummary;
if (chkVisibleSeal.isSelected()) { if (chkVisibleSeal.isSelected()) {
String themeName = theme.name(); 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) { if (isCustomPositionActive && customPageNumber != null && customPdfX != null && customPdfY != null) {
sealSummary = String.format("Selo Estampado (%s) — Pág. %d [X: %.0f pt, Y: %.0f pt]", sealSummary = String.format("Selo Estampado (%s)%s — Pág. %d [X: %.0f pt, Y: %.0f pt]",
themeName, customPageNumber, customPdfX, customPdfY); themeName, scaleInfo, customPageNumber, customPdfX, customPdfY);
} else { } else {
sealSummary = String.format("Selo Estampado (%s) — %s (%s)", sealSummary = String.format("Selo Estampado (%s)%s — %s (%s)",
themeName, (pos != null ? pos.label : "Canto Inferior Direito"), (pg != null ? pg.label : "Última Página")); themeName, scaleInfo, (pos != null ? pos.label : "Canto Inferior Direito"), (pg != null ? pg.label : "Última Página"));
} }
} else { } else {
sealSummary = "Sem Selo Visual (Assinatura PAdES Invisível)"; sealSummary = "Sem Selo Visual (Assinatura PAdES Invisível)";