Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
projeto-recortador-pdf
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Pedro Bueno
projeto-recortador-pdf
Commits
9856f689
Commit
9856f689
authored
May 14, 2026
by
Tiago da Silva Sabaini
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Ajuste final CSS e HTML
parent
b23f3e60
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
285 additions
and
85 deletions
+285
-85
app.py
app.py
+8
-1
style.css
static/style.css
+212
-10
index.html
templates/index.html
+65
-74
No files found.
app.py
View file @
9856f689
...
@@ -30,7 +30,14 @@ def upload_preview():
...
@@ -30,7 +30,14 @@ def upload_preview():
pdf
=
request
.
files
[
'pdf'
]
pdf
=
request
.
files
[
'pdf'
]
pdf_path
=
os
.
path
.
join
(
UPLOAD_FOLDER
,
pdf
.
filename
)
pdf_path
=
os
.
path
.
join
(
UPLOAD_FOLDER
,
pdf
.
filename
)
pdf
.
save
(
pdf_path
)
pdf
.
save
(
pdf_path
)
return
{
"filename"
:
pdf
.
filename
}
# NOVO: Abre o PDF para descobrir o total de páginas
doc
=
fitz
.
open
(
pdf_path
)
total_pages
=
len
(
doc
)
doc
.
close
()
# Retorna o nome e o total de páginas
return
{
"filename"
:
pdf
.
filename
,
"total_pages"
:
total_pages
}
@app.route
(
'/preview/<filename>/<int:pagnumber>'
)
@app.route
(
'/preview/<filename>/<int:pagnumber>'
)
def
preview
(
filename
,
pagnumber
):
def
preview
(
filename
,
pagnumber
):
...
...
static/style.css
View file @
9856f689
body
{
background
:
#121212
;
color
:
#eee
;
font-family
:
sans-serif
;
display
:
flex
;
justify-content
:
center
;
padding
:
20px
;
}
:root
{
.container
{
background
:
#1e1e1e
;
padding
:
30px
;
border-radius
:
15px
;
width
:
100%
;
max-width
:
700px
;
box-shadow
:
0
10px
30px
rgba
(
0
,
0
,
0
,
0.5
);
text-align
:
center
;
}
--primary-color
:
#e5322d
;
h1
{
color
:
#4CAF50
;
}
--primary-hover
:
#c42723
;
.custom-file-upload
{
border
:
2px
dashed
#4CAF50
;
padding
:
20px
;
cursor
:
pointer
;
display
:
block
;
border-radius
:
10px
;
background
:
#252525
;
margin-bottom
:
20px
;
}
--bg-color
:
#f5f5f7
;
.row
{
display
:
flex
;
gap
:
15px
;
margin-bottom
:
20px
;
}
--text-dark
:
#333333
;
.input-group
{
flex
:
1
;
text-align
:
left
;
}
--text-muted
:
#666666
;
input
[
type
=
"number"
]
{
width
:
100%
;
padding
:
10px
;
background
:
#333
;
border
:
1px
solid
#444
;
color
:
white
;
border-radius
:
5px
;
}
--white
:
#ffffff
;
.btn-primary
{
width
:
100%
;
padding
:
15px
;
background
:
#4CAF50
;
border
:
none
;
color
:
white
;
font-weight
:
bold
;
border-radius
:
8px
;
cursor
:
pointer
;
}
--border-radius
:
12px
;
.footer
{
margin-top
:
20px
;
font-size
:
11px
;
color
:
#666
;
}
--shadow
:
0
10px
30px
rgba
(
0
,
0
,
0
,
0.08
);
\ No newline at end of file
}
*
{
margin
:
0
;
padding
:
0
;
box-sizing
:
border-box
;
font-family
:
'Inter'
,
sans-serif
;
}
body
{
background-color
:
var
(
--bg-color
);
color
:
var
(
--text-dark
);
min-height
:
100vh
;
display
:
flex
;
flex-direction
:
column
;
}
header
{
background-color
:
var
(
--white
);
padding
:
20px
40px
;
box-shadow
:
0
2px
10px
rgba
(
0
,
0
,
0
,
0.05
);
display
:
flex
;
align-items
:
center
;
}
.logo
{
font-size
:
24px
;
font-weight
:
800
;
color
:
var
(
--text-dark
);
text-decoration
:
none
;
display
:
flex
;
align-items
:
center
;
gap
:
10px
;
}
.logo
span
{
color
:
var
(
--primary-color
);
}
main
{
flex
:
1
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
padding
:
60px
20px
;
text-align
:
center
;
}
h1
{
font-size
:
42px
;
font-weight
:
800
;
margin-bottom
:
15px
;
color
:
var
(
--text-dark
);
}
.subtitle
{
font-size
:
18px
;
color
:
var
(
--text-muted
);
max-width
:
600px
;
margin-bottom
:
40px
;
line-height
:
1.6
;
}
.upload-area
{
background-color
:
var
(
--white
);
width
:
100%
;
max-width
:
800px
;
padding
:
60px
20px
;
border-radius
:
var
(
--border-radius
);
box-shadow
:
var
(
--shadow
);
border
:
2px
dashed
#d1d5db
;
transition
:
all
0.3s
ease
;
}
.upload-area
:hover
{
border-color
:
var
(
--primary-color
);
}
.custom-file-upload
{
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
gap
:
15px
;
background-color
:
var
(
--primary-color
);
color
:
var
(
--white
);
font-size
:
24px
;
font-weight
:
600
;
padding
:
20px
40px
;
border-radius
:
8px
;
cursor
:
pointer
;
transition
:
background
0.3s
ease
,
transform
0.2s
ease
;
box-shadow
:
0
4px
15px
rgba
(
229
,
50
,
45
,
0.3
);
}
.custom-file-upload
:hover
{
background-color
:
var
(
--primary-hover
);
transform
:
translateY
(
-2px
);
}
.upload-hint
{
margin-top
:
15px
;
font-size
:
14px
;
color
:
var
(
--text-muted
);
}
.how-it-works
{
margin-top
:
80px
;
display
:
flex
;
gap
:
30px
;
max-width
:
900px
;
text-align
:
left
;
}
.feature-card
{
background
:
var
(
--white
);
padding
:
25px
;
border-radius
:
8px
;
box-shadow
:
0
4px
6px
rgba
(
0
,
0
,
0
,
0.04
);
flex
:
1
;
}
.feature-card
h3
{
font-size
:
18px
;
margin-bottom
:
10px
;
color
:
var
(
--primary-color
);
}
.feature-card
p
{
font-size
:
14px
;
color
:
var
(
--text-muted
);
line-height
:
1.5
;
}
#tool-workspace
{
display
:
none
;
width
:
100%
;
max-width
:
1000px
;
background
:
var
(
--white
);
padding
:
30px
;
border-radius
:
var
(
--border-radius
);
box-shadow
:
var
(
--shadow
);
margin-top
:
40px
;
}
#preview-wrapper
{
position
:
relative
;
display
:
inline-block
;
border
:
1px
solid
#ccc
;
line-height
:
0
;
overflow
:
hidden
;
user-select
:
none
;
margin-top
:
20px
;
box-shadow
:
0
4px
10px
rgba
(
0
,
0
,
0
,
0.1
);
}
#pdf-img
{
max-width
:
100%
;
display
:
block
;
}
#selector
{
position
:
absolute
;
border
:
2px
dashed
var
(
--primary-color
);
background
:
rgba
(
229
,
50
,
45
,
0.1
);
cursor
:
move
;
box-sizing
:
border-box
;
width
:
150px
;
height
:
150px
;
top
:
0
;
left
:
0
;
}
#resizer
{
position
:
absolute
;
right
:
-2px
;
bottom
:
-2px
;
width
:
16px
;
height
:
16px
;
background
:
var
(
--primary-color
);
cursor
:
nwse-resize
;
border-radius
:
50%
;
}
.nav-controls
{
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
gap
:
20px
;
margin-bottom
:
20px
;
}
.nav-btn
,
.btn-primary
{
background
:
#333
;
color
:
white
;
border
:
none
;
padding
:
10px
20px
;
border-radius
:
5px
;
cursor
:
pointer
;
font-weight
:
600
;
transition
:
background
0.2s
;
}
.nav-btn
:hover
{
background
:
#555
;
}
.btn-primary
{
background
:
var
(
--primary-color
);
font-size
:
16px
;
margin-top
:
20px
;
width
:
100%
;
padding
:
15px
;}
.btn-primary
:hover
{
background
:
var
(
--primary-hover
);
}
.row
{
display
:
flex
;
gap
:
20px
;
margin-top
:
20px
;
justify-content
:
center
;}
.input-group
label
{
display
:
block
;
font-size
:
14px
;
font-weight
:
600
;
margin-bottom
:
5px
;
text-align
:
left
;
}
.input-group
input
{
padding
:
10px
;
border
:
1px
solid
#ccc
;
border-radius
:
5px
;
width
:
120px
;
text-align
:
center
;
font-size
:
16px
;}
footer
{
text-align
:
center
;
padding
:
20px
;
color
:
var
(
--text-muted
);
font-size
:
14px
;
border-top
:
1px
solid
#eaeaea
;
background
:
var
(
--white
);
}
\ No newline at end of file
templates/index.html
View file @
9856f689
...
@@ -2,80 +2,54 @@
...
@@ -2,80 +2,54 @@
<html
lang=
"pt-br"
>
<html
lang=
"pt-br"
>
<head>
<head>
<meta
charset=
"UTF-8"
>
<meta
charset=
"UTF-8"
>
<title>
PDF Visual - Pedro Bueno
</title>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
>
<title>
Captura de PDF - Recorte e Extraia Áreas
</title>
<link
href=
"https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap"
rel=
"stylesheet"
>
<link
rel=
"stylesheet"
href=
"/static/style.css"
>
<link
rel=
"stylesheet"
href=
"/static/style.css"
>
<style>
#preview-container
{
display
:
none
;
margin
:
20px
auto
;
text-align
:
center
;
}
#preview-wrapper
{
position
:
relative
;
display
:
inline-block
;
border
:
2px
solid
#555
;
line-height
:
0
;
overflow
:
hidden
;
/* Garante que nada saia visualmente */
user-select
:
none
;
}
#pdf-img
{
max-width
:
100%
;
display
:
block
;
}
#selector
{
position
:
absolute
;
border
:
2px
dashed
#4CAF50
;
background
:
rgba
(
76
,
175
,
80
,
0.2
);
cursor
:
move
;
box-sizing
:
border-box
;
width
:
150px
;
height
:
150px
;
top
:
0
;
left
:
0
;
}
#resizer
{
position
:
absolute
;
right
:
0
;
bottom
:
0
;
width
:
15px
;
height
:
15px
;
background
:
#4CAF50
;
cursor
:
nwse-resize
;
}
.nav-controls
{
margin
:
15px
0
;
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
gap
:
20px
;
}
.nav-btn
{
background
:
#444
;
color
:
white
;
border
:
none
;
padding
:
10px
20px
;
border-radius
:
5px
;
cursor
:
pointer
;
}
.nav-btn
:hover
{
background
:
#555
;
}
</style>
</head>
</head>
<body>
<body>
<div
class=
"container"
>
<h1>
Recortador Visual 2.0 🚀
</h1>
<header>
<a
href=
"#"
class=
"logo"
>
<svg
width=
"24"
height=
"24"
viewBox=
"0 0 24 24"
fill=
"none"
stroke=
"#e5322d"
stroke-width=
"2"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"
></path><polyline
points=
"14 2 14 8 20 8"
></polyline><line
x1=
"16"
y1=
"13"
x2=
"8"
y2=
"13"
></line><line
x1=
"16"
y1=
"17"
x2=
"8"
y2=
"17"
></line><polyline
points=
"10 9 9 9 8 9"
></polyline></svg>
Corta Aí
<span>
PDF
</span>
</a>
</header>
<main>
<h1>
Captura de PDF
</h1>
<p
class=
"subtitle"
>
A ferramenta mais rápida e visual para selecionar, recortar e extrair partes específicas de qualquer página do seu documento PDF.
</p>
<div
class=
"upload-area"
id=
"upload-section"
>
<label
for=
"pdf-file"
class=
"custom-file-upload"
>
<label
for=
"pdf-file"
class=
"custom-file-upload"
>
📁 Selecionar PDF para Visualização
📥 Selecionar arquivo PDF
</label>
</label>
<input
type=
"file"
id=
"pdf-file"
accept=
"application/pdf"
onchange=
"uploadAndPreview()"
style=
"display:none;"
>
<input
type=
"file"
id=
"pdf-file"
accept=
"application/pdf"
onchange=
"iniciarApp()"
style=
"display:none;"
>
<p
class=
"upload-hint"
>
ou arraste e solte o PDF aqui
</p>
<div
id=
"loading"
style=
"display:none; color: var(--primary-color); margin-top: 15px; font-weight: 600;"
>
⏳ Processando o documento...
</div>
</div>
<div
id=
"loading"
style=
"display:none; color: #4CAF50;"
>
⏳ Carregando página...
</div>
<div
class=
"how-it-works"
id=
"instructions"
>
<div
class=
"feature-card"
>
<h3>
1. Envie o arquivo
</h3>
<p>
Selecione o PDF do seu computador. Tudo é processado rapidamente na tela.
</p>
</div>
<div
class=
"feature-card"
>
<h3>
2. Ajuste a área
</h3>
<p>
Use nossa caixa de seleção interativa. Arraste e dimensione exatamente onde deseja recortar.
</p>
</div>
<div
class=
"feature-card"
>
<h3>
3. Baixe o resultado
</h3>
<p>
Defina o intervalo de páginas e faça o download automático das áreas capturadas.
</p>
</div>
</div>
<div
id=
"
preview-container
"
>
<div
id=
"
tool-workspace
"
>
<div
class=
"nav-controls"
>
<div
class=
"nav-controls"
>
<button
type=
"button"
class=
"nav-btn"
onclick=
"changePage(-1)"
>
⬅️
Anterior
</button>
<button
type=
"button"
class=
"nav-btn"
onclick=
"changePage(-1)"
>
Anterior
</button>
<span
id=
"page-info"
>
Página: 0
</span>
<span
id=
"page-info"
style=
"font-weight: bold; font-size: 18px;"
>
Página: 0
</span>
<button
type=
"button"
class=
"nav-btn"
onclick=
"changePage(1)"
>
Próxima
➡️
</button>
<button
type=
"button"
class=
"nav-btn"
onclick=
"changePage(1)"
>
Próxima
</button>
</div>
</div>
<div
id=
"preview-wrapper"
>
<div
id=
"preview-wrapper"
>
...
@@ -84,7 +58,6 @@
...
@@ -84,7 +58,6 @@
<div
id=
"resizer"
></div>
<div
id=
"resizer"
></div>
</div>
</div>
</div>
</div>
</div>
<form
action=
"/processar"
method=
"post"
id=
"main-form"
>
<form
action=
"/processar"
method=
"post"
id=
"main-form"
>
<input
type=
"hidden"
name=
"filename"
id=
"hidden-filename"
>
<input
type=
"hidden"
name=
"filename"
id=
"hidden-filename"
>
...
@@ -93,26 +66,38 @@
...
@@ -93,26 +66,38 @@
<div
class=
"row"
>
<div
class=
"row"
>
<div
class=
"input-group"
>
<div
class=
"input-group"
>
<label>
Página Início:
</label>
<label>
Página Início:
</label>
<input
type=
"number"
name=
"inicio"
id=
"input-inicio"
value
=
"0"
>
<input
type=
"number"
name=
"inicio"
id=
"input-inicio"
value=
"0"
min
=
"0"
>
</div>
</div>
<div
class=
"input-group"
>
<div
class=
"input-group"
>
<label>
Página Fim:
</label>
<label>
Página Fim:
</label>
<input
type=
"number"
name=
"fim"
value=
"1
"
>
<input
type=
"number"
name=
"fim"
id=
"input-fim"
value=
"1"
min=
"0
"
>
</div>
</div>
</div>
</div>
<button
type=
"submit"
class=
"btn-primary"
>
GERAR E BAIXAR ZIP
</button>
<button
type=
"submit"
class=
"btn-primary"
>
GERAR RECORTE
</button>
</form>
</form>
<p
class=
"footer"
>
© Pedro Bueno
</p>
</div>
</div>
</main>
<footer>
<p>
© 2026 Corta Aí PDF - Criado por os GOATS.
</p>
</footer>
<script>
<script>
function
iniciarApp
()
{
document
.
getElementById
(
'instructions'
).
style
.
display
=
'none'
;
document
.
getElementById
(
'upload-section'
).
style
.
padding
=
'30px 20px'
;
uploadAndPreview
();
}
const
selector
=
document
.
getElementById
(
'selector'
);
const
selector
=
document
.
getElementById
(
'selector'
);
const
resizer
=
document
.
getElementById
(
'resizer'
);
const
resizer
=
document
.
getElementById
(
'resizer'
);
const
img
=
document
.
getElementById
(
'pdf-img'
);
const
img
=
document
.
getElementById
(
'pdf-img'
);
const
coordsInput
=
document
.
getElementById
(
'coords-input'
);
const
coordsInput
=
document
.
getElementById
(
'coords-input'
);
const
inputInicio
=
document
.
getElementById
(
'input-inicio'
);
const
inputInicio
=
document
.
getElementById
(
'input-inicio'
);
const
inputFim
=
document
.
getElementById
(
'input-fim'
);
let
currentPage
=
0
;
let
currentPage
=
0
;
let
totalPages
=
0
;
async
function
uploadAndPreview
()
{
async
function
uploadAndPreview
()
{
const
file
=
document
.
getElementById
(
'pdf-file'
).
files
[
0
];
const
file
=
document
.
getElementById
(
'pdf-file'
).
files
[
0
];
...
@@ -126,7 +111,14 @@
...
@@ -126,7 +111,14 @@
const
data
=
await
res
.
json
();
const
data
=
await
res
.
json
();
document
.
getElementById
(
'hidden-filename'
).
value
=
data
.
filename
;
document
.
getElementById
(
'hidden-filename'
).
value
=
data
.
filename
;
totalPages
=
data
.
total_pages
;
inputInicio
.
max
=
totalPages
-
1
;
inputFim
.
max
=
totalPages
-
1
;
currentPage
=
parseInt
(
inputInicio
.
value
)
||
0
;
currentPage
=
parseInt
(
inputInicio
.
value
)
||
0
;
if
(
currentPage
>=
totalPages
)
currentPage
=
totalPages
-
1
;
updatePreview
();
updatePreview
();
}
}
...
@@ -134,11 +126,11 @@
...
@@ -134,11 +126,11 @@
const
filename
=
document
.
getElementById
(
'hidden-filename'
).
value
;
const
filename
=
document
.
getElementById
(
'hidden-filename'
).
value
;
img
.
src
=
`/preview/
${
filename
}
/
${
currentPage
}
?t=
${
new
Date
().
getTime
()}
`
;
img
.
src
=
`/preview/
${
filename
}
/
${
currentPage
}
?t=
${
new
Date
().
getTime
()}
`
;
document
.
getElementById
(
'page-info'
).
innerText
=
`Visualizando Página:
${
currentPage
}
`
;
document
.
getElementById
(
'page-info'
).
innerText
=
`Visualizando Página:
${
currentPage
}
`
;
inputInicio
.
value
=
currentPage
;
// Sincroniza o form com a visão
inputInicio
.
value
=
currentPage
;
img
.
onload
=
()
=>
{
img
.
onload
=
()
=>
{
document
.
getElementById
(
'loading'
).
style
.
display
=
'none'
;
document
.
getElementById
(
'loading'
).
style
.
display
=
'none'
;
document
.
getElementById
(
'
preview-container
'
).
style
.
display
=
'block'
;
document
.
getElementById
(
'
tool-workspace
'
).
style
.
display
=
'block'
;
updateCoords
();
updateCoords
();
};
};
}
}
...
@@ -146,6 +138,7 @@
...
@@ -146,6 +138,7 @@
function
changePage
(
delta
)
{
function
changePage
(
delta
)
{
currentPage
+=
delta
;
currentPage
+=
delta
;
if
(
currentPage
<
0
)
currentPage
=
0
;
if
(
currentPage
<
0
)
currentPage
=
0
;
if
(
currentPage
>=
totalPages
)
currentPage
=
totalPages
-
1
;
updatePreview
();
updatePreview
();
}
}
...
@@ -175,7 +168,6 @@
...
@@ -175,7 +168,6 @@
let
x
=
e
.
clientX
-
startX
;
let
x
=
e
.
clientX
-
startX
;
let
y
=
e
.
clientY
-
startY
;
let
y
=
e
.
clientY
-
startY
;
// Bloqueia saída das bordas
if
(
x
<
0
)
x
=
0
;
if
(
x
<
0
)
x
=
0
;
if
(
y
<
0
)
y
=
0
;
if
(
y
<
0
)
y
=
0
;
if
(
x
+
selector
.
offsetWidth
>
img
.
offsetWidth
)
x
=
img
.
offsetWidth
-
selector
.
offsetWidth
;
if
(
x
+
selector
.
offsetWidth
>
img
.
offsetWidth
)
x
=
img
.
offsetWidth
-
selector
.
offsetWidth
;
...
@@ -188,7 +180,6 @@
...
@@ -188,7 +180,6 @@
let
newWidth
=
startW
+
(
e
.
clientX
-
startX
);
let
newWidth
=
startW
+
(
e
.
clientX
-
startX
);
let
newHeight
=
startH
+
(
e
.
clientY
-
startY
);
let
newHeight
=
startH
+
(
e
.
clientY
-
startY
);
// Bloqueia redimensionamento para fora da imagem
if
(
selector
.
offsetLeft
+
newWidth
>
img
.
offsetWidth
)
newWidth
=
img
.
offsetWidth
-
selector
.
offsetLeft
;
if
(
selector
.
offsetLeft
+
newWidth
>
img
.
offsetWidth
)
newWidth
=
img
.
offsetWidth
-
selector
.
offsetLeft
;
if
(
selector
.
offsetTop
+
newHeight
>
img
.
offsetHeight
)
newHeight
=
img
.
offsetHeight
-
selector
.
offsetTop
;
if
(
selector
.
offsetTop
+
newHeight
>
img
.
offsetHeight
)
newHeight
=
img
.
offsetHeight
-
selector
.
offsetTop
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment