Native support for perspective-transformed text rendering (PostScript-level transformation)
I maintainer di solito rispondono entro 1 giorno
@Esteban82 ci sta già lavorando.
Dal 18/2/2026.
Valutazione
Questa issue non è ancora stata valutata.
Descrizione
I would like to share a PostScript prototype I implemented and ask for feedback before attempting to integrate this into the GMT source code.
This is related to #8816, but I decided to post it separately for clarity.
I implemented a PostScript prototype that applies a backward rotation around the bottom edge of a text object, followed by a perspective projection. The visual effect is similar to the perspective tool in GIMP. The prototype is intended for text elements such as titles, for now.
How it would be used
The user would only need to provide a rotation angle and a Field-of-View (FOV) (as in GIMP).
All other parameters (text bounding box, rotation axis, focal distance, vanishing point, depth scaling) are computed automatically.
The rotation axis and vanishing point are hire-wire the the bottom edge and the center of figure respectively.
How it would be used
The user would only need to provide:
- A rotation angle
- A Field-of-View (FOV)
Conceptually, something like:
+p<angle>/<fov>
All other parameters (bounding box, focal distance, depth scaling, etc.) are computed automatically.
Currently, the rotation axis and vanishing point are hard-wired to:
- The bottom edge of the text (rotation axis)
- The center of the figure (vanishing point)
However, both could be made configurable without major changes.
Current Question
Before moving toward a C-level implementation inside GMT (likely within PSL or the text rendering logic), I would appreciate feedback on the PostScript prototype.
In particular:
- Are there obvious inefficiencies in the current path transformation approach?
- Is there an existing PSL mechanism that could simplify this?
- Any recommendations to improve performance or numerical stability?
My goal is to ensure the PostScript logic is clean and efficient before porting it into the GMT source code.
Postscript prototype
Output:
File:
%!PS-Adobe-3.0
%%BoundingBox: 0 0 245 100
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 1. USER PARAMETERS
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
/ang_rotation 30 def % Rotation angle (degrees)
/fov 45 def % Field of view (perspective angle in degrees)
/texto {
(Episode IV)
} def % Text to render
/Times-Roman findfont 54 scalefont setfont % Font configuration
/z_near 0 def % Canvas is located at z = 0 (added just in case)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 2. BASIC MATH UTILITIES
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Basic trigonometric functions. This could be done in C.
/tan { dup sin exch cos div } bind def
/tan_f { fov 2 div tan } bind def
/cos_ang_rotation { ang_rotation cos } bind def
/sin_ang_rotation { ang_rotation sin } bind def
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 3. TEXT BOUNDING BOX
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Computes bounding box and dimensions of the text path
/Get_Text_Bounding_Box {
true charpath
pathbbox
/top exch def
/right exch def
/bottom exch def
/left exch def
/height top bottom sub def
/width right left sub def
} bind def
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 4. ROTATION SETUP
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
/Rotation_Setup {
% Use bottom edge as rotation axis (it can be modified)
/axisOriginY bottom def
} bind def
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 5. PERSPECTIVE SETUP
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
/Calculate_Focal_distance {
/focal height 2 div tan_f div def
} bind def
/Set_Vanishing_point { % Set at the center of the figure
/Cx right left add 2 div def % Vanishing point X (center)
/Cy top bottom add 2 div def % Vanishing point Y (center)
} bind def
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 6. PATH TRANSFORMATION UTILITIES from Bill Casselman.
%% https://personal.math.ubc.ca/~cass/graphics/manual/transform.inc
%% I have his permission to use it.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% The original file includes a subdivide procedure. I am not using it.
% But maybe it can be included.
/ctransform {
load
8 dict begin
/f exch def
[
{ [ 3 1 roll f {moveto} ] }
{ [ 3 1 roll f {lineto} ] }
{ [ 7 1 roll f 6 2 roll f 6 2 roll f 6 2 roll {curveto} ] }
{ [ {closepath} ] }
pathforall
]
newpath
{ aload pop exec } forall
end
} def
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 7. ROTATION + PERSPECTIVE PROJECTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Transforms (x, y) -> (xp, yp)
/rotate-project {
/y exch def
/x exch def
% Compute depth (z') after rotation
y axisOriginY sub
sin_ang_rotation mul
z_near add
/z exch def
% Perspective scaling factor (depends on Z)
/s z focal add focal exch div def
% Transform X
x Cx sub s mul Cx add
% Rotate Y around axis, then apply perspective
y axisOriginY sub
cos_ang_rotation mul
axisOriginY add
Cy sub s mul Cy add
} bind def
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 8. RENDERING
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
0 62 translate
0.6 setgray
% --- Compute text metrics ---
newpath
0 0 moveto
texto
Get_Text_Bounding_Box
Rotation_Setup
Calculate_Focal_distance
Set_Vanishing_point
% --- Draw original text (gray) ---
newpath
0 -50 moveto
texto
show
% --- Draw perspective-transformed text (red) ---
newpath
0 0 moveto
texto
true charpath
/rotate-project ctransform
1 0 0 setrgbcolor
fill
showpage
- Lingua principale
- C
- Stelle
- 984
- Fork
- 413
- Merge medio
- 21h 18m
- PR unite (30g)
- 39
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di GenericMappingTools/gmt
-
documentation
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
GenericMappingTools/gmt#5767 · 15 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
feature request
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
GenericMappingTools/gmt#9225 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
inset: Specifing the region using -Dxmin/xmax/ymin/ymax doesn't workForse già presa @Esteban82 l’ha presa 15 giorni fa. Apertabug
GenericMappingTools/gmt#9224 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
histogram: Support density histograms and moreForse già presa @Esteban82 l’ha presa 23 giorni fa. Apertafeature request
GenericMappingTools/gmt#9212 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Release GMT 6.7.1Aperta
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
GenericMappingTools/gmt#9202 · 7 commenti ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di GenericMappingTools/gmt
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
I maintainer di solito rispondono entro 1 giorno
-
severity: low
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
luainkernel/lunatik#1853 ·
I maintainer di solito rispondono entro 1 giorno
-
encoding.binary: bounds check guard is compiled away, so decode functions read past the sliceAperta
Difficoltà 2/5 Mezza giornata Idoneità per principianti 70/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
resetes12/pokeemerald#204 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
I maintainer di solito rispondono entro 6 giorni