feat(pdf): suporte a redimensionamento interativo e proporcional do selo de assinatura
This commit is contained in:
+27
-4
@@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -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)
|
||||||
|
|||||||
+24
-3
@@ -66,8 +66,28 @@ public final class PdfCertificateSignDialog {
|
|||||||
Float customY,
|
Float customY,
|
||||||
Integer customPageNumber,
|
Integer customPageNumber,
|
||||||
String reason,
|
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 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());
|
||||||
|
|||||||
+185
-39
@@ -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)";
|
||||||
|
|||||||
Reference in New Issue
Block a user