How can I have text shrink automatically depending on the character length in Postscipt? Or to have the text resize in order to fit a fixed area? - text

How can I have the text size shrink automatically depending on character length in Postscipt? Or to have the text resize in order to fit a fixed area?

I think it's simpler to make a 1-pt font, measure,and then scale up. Something like (untested)
/showwid { % string width /font-name
gsave
1 selectfont
1 index stringwidth pop div % width/stringwidth
currentfont scalefont setfont
show
grestore
} def
This code doesn't account for height at all.

The way i do it is:
get string width
divide area width by string width
multiply current font size by that
then use this as new font size and show
So, the code is something like:
%some bogus vars to make it understandable
/myfont /Helvetica def
/mysz 10 def
/mywidth 100 def
/mystr (Hello PS world!) def
%supposedly you'd be in the middle of something
%therefore we would have the font already selected
myfont mysz selectfont
50 50 moveto
myfont mywidth mystr stringwidth pop div mysz mul selectfont
mystr show
% and for good measure, a line showing the size
50 49 moveto
mywidth 0 rlineto stroke

Related

Shading a triangle in postscript

I want to write some postscript code that will shade a triangle. Here is some code I have cobbled together from a couple of google searches that produces a shaded square:
%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: 0 0 400 400
/square{
0 0 40 40 rectclip
<< /ShadingType 2
/ColorSpace [ /DeviceRGB ]
/Coords [ 0 0 40 40 ]
/Function <<
/FunctionType 2
/Domain [ 0 1 ]
/C0 [ 0.9 0.2 0.0 ]
/C1 [ 0.0 0.2 0.9 ]
/N 1
>>
>>
} bind def
save gsave
200 200 translate 45 rotate
square shfill
grestore restore
100 100 translate 300 rotate
square shfill showpage
Now, if I create a plain text file with this code and give it the filename shade.ps --- the important thing being the .ps --- and double click to open on a mac then the Apple Preview app will open the file and produce a picture with two copies of the shaded square. Great!
But now I want to shade a triangle in a similar way. My goal is to have a few lines of code in the spirit of the above that produces a shaded triangle.
Can anyone help? Is this even the right forum to be asking this question?
Note added 16 Jan 2023: I removed the redundant line of code mentioned in the answer below.
My goal is to draw a Penrose tiling with thin and thick rhombuses and then shade the rhombuses to help give the appearance that they are sloping rooves in the spirit of John Conway's city named Penrasia.
The part that describes the square is this line:
0 0 40 40 rectclip
To change it into a triangle, you'll need to write a function that creates a triangular path and then calls clip on it. This may be easier to understand if you look at an expanded version of rectclip which would look something like this:
/rectclip {
4 dict
/height exch def
/width exch def
/y exch def
/x exch def
x y moveto
width 0 rlineto
0 height rlineto
width neg 0 rlineto
closepath
clip
end
} def
For a triangle, you'll need to do something more like this, depending upon how you want to describe the triangle:
/triclip {
4 dict
/height exch def
/width exch def
/y exch def
/x exch def
x y moveto
width 0 rlineto
width 2 div neg height rlineto
closepath
clip
end
} def
And you'd call this function the same way that rectclip is called in the original.
0 0 40 40 triclip
A more idiomatic way to write the function in PostScript would use stack manipulation to avoid the need for a local dictionary.
/triclip { % x y width height . - ;sets clippath
4 2 roll % |- width height x y
moveto % |- width height
exch dup 0 % |- height width width 0
rlineto % |- height width
2 div neg exch % |- -width/2 height
rlineto % |-
closepath clip
} def
With practice you can start to "read" the stack manipulation part and treat the code instead as several statements and break the lines differently:
/triclip { % x y width height . - ;sets clippath
4 2 roll moveto % width height
exch dup 0 rlineto % height width
2 div neg exch rlineto % -
closepath clip
} def
The important part is the calls to moveto, lineto, closepath, and clip, and the rest is just the "syntax" of PostScript.

Convert Excel column width between characters unit and pixels (points)

"One unit of column width is equal to the width of one character in the Normal style. For proportional fonts, the width of the character 0 (zero) is used."
So ColumnWidth in Excel is measured as a number of "0" characters which fits in a column. How can this value be converted into pixels and vice versa?
As already mentioned ColumnWidth value in Excel depends on default font of a Workbook which can be obtained via Workbook.Styles("Normal").Font. Also it depends on current screen DPI.
After carrying out some research for different fonts and sizes in Excel 2013 I've found out that we have 2 linear functions (Arial cannot be seen because it overlaps with Tahoma.):
As it can be seen in the picture the function for ColumnWidth < 1 is different from the major part of the line chart. It's calculated as a number of pixels in a column / number of pixels needed to fit one "0" character in a column.
Now let's see what a typical cell width consists of.
A - "0" character width in the Normal Style
B - left and right padding
C - 1px right margin
A can be calculated with GetTextExtentPoint32 Windows API function, but font size should be a little bit bigger. By experiment I chose +0.3pt which worked for me for different fonts with 8-48pt base size. B is (A + 1) / 4 rounded to integer using "round half up". Also screen DPI will be needed here (see Python 3 implementation below)
Here are equations for character-pixel conversion and their implementation in Python 3:
import win32print, win32gui
from math import floor
def get_screen_dpi():
dc = win32gui.GetDC(0)
LOGPIXELSX, LOGPIXELSY = 88, 90
dpi = [win32print.GetDeviceCaps(dc, i) for i in (LOGPIXELSX,
LOGPIXELSY)]
win32gui.ReleaseDC(0, dc)
return dpi
def get_text_metrics(fontname, fontsize):
"Measures '0' char size for the specified font name and size in pt"
dc = win32gui.GetDC(0)
font = win32gui.LOGFONT()
font.lfFaceName = fontname
font.lfHeight = -fontsize * dpi[1] / 72
hfont = win32gui.CreateFontIndirect(font)
win32gui.SelectObject(dc, hfont)
metrics = win32gui.GetTextExtentPoint32(dc, "0")
win32gui.ReleaseDC(0, dc)
return metrics
def ch_px(v, unit="ch"):
"""
Convert between Excel character width and pixel width.
`unit` - unit to convert from: 'ch' (default) or 'px'
"""
rd = lambda x: floor(x + 0.5) # round half up
# pad = left cell padding + right cell padding + cell border(1)
pad = rd((z + 1) / 4) * 2 + 1
z_p = z + pad # space (px) for "0" character with padding
if unit == "ch":
return v * z_p if v < 1 else v * z + pad
else:
return v / z_p if v < z_p else (v - pad) / z
font = "Calibri", 11
dpi = get_screen_dpi()
z = get_text_metrics(font[0], font[1] + 0.3)[0] # "0" char width in px
px = ch_px(30, "ch")
ch = ch_px(px, "px")
print("Characters:", ch, "Pixels:", px, "for", font)
2022 and still the same Problem... Found threads going back to 2010 having the issue...
To start of: Pixel != Points
Points are defined as 72points/inch: https://learn.microsoft.com/en-us/office/vba/language/glossary/vbe-glossary#point
Though that definition seems stupid, as a shape with a fixed width of 100points, would display the exact same size in inch on every monitor independent of monitor configuration, which is not the case.
Characters is a unit that is defined to the number of 0 characters of the default text format. A cell set to a width of 10 characters, can fit 10 "0" characters, when the cell content is formatted to the default format.
My case is that I need to place pictures into the document and place text into cells next to it. But pictures hover over the document and cells are hidden below it. Depending on the size of the Picture, more or less cells are hidden. Thus, I can't just say I place text 5 cells to the left of the picture. Autosizing a column to the contents of the cells of the column, does not account for the hovering picture.
A picture is bound to the cell that is below the top left corner of the picture. I need to set the size of that cell to the size of the picture to solve the issue.
A Picture is a Shape. A Shape returns its width as Points (Shape.Width).
A Range can be set to a cell like Worksheet.Range["A1"]. From a Range you can get the width in Characters (Range.ColumnWidth) or in Points (Range.Width). But you can only set the width of a Range in Characters (Range.ColumnWidth).
So we can retrieve the size of the Picture (Shape) in Points and need to convert them to Characters to set the cell to the correct width...
Some research showed that the Points size of a cell contains a constant for spacing (padding before and after the cell content) and probably the seperator lines between cells.
On my system:
A cell set to a width of 1 **Characters** = 9 **Points**
A cell set to a width of 2 **Characters** = 14.25 **Points**
A cell set to a width of 3 **Characters** = 19.5 **Points**
As I said, there is a constant within the Points. Thus going from 1 Characters, to 2 Characters, the difference is only the size of the letter.
SizeOfLetter = 14.25 Points - 9 Points = 5.25 Points
we can then subtract that SizeOfLetter from the Points for 1 Characters and get the Points constant.
PointsConstant = 9 Points - 5.25 Points = 3.75 Points
Verify:
Points size for a cell containing 3 "0" letters = 3SizeOfLetter + PointsConstant = 35.25 Points + 3.75 Points = 19.5 Points
As the values depend on your system, YOU CAN'T USE THOSE VALUES!
Best way is to use code to calculate it for your system:
C# code:
Excel.Application excelApp = new Excel.Application();
Excel.Workbook workbook1 = excelApp.Workbooks.Add();
Excel.Worksheet sheet1 = (Excel.Worksheet)workbook1.ActiveSheet;
// Evaluate the Points data for the document
double previousColumnWidth = (double)sheet1.Range["A1"].ColumnWidth;
sheet1.Range["A1"].ColumnWidth = 1; // Make the cell fit 1 character
double points1 = (double)sheet1.Range["A1"].Width;
sheet1.Range["A1"].ColumnWidth = 2; // Make the cell fit 2 characters
double points2 = (double)sheet1.Range["A1"].Width;
double SizeOfLetter = points2 - points1;
double PointsConstant = points1 - pointsPerCharater;
// Reset the column width
sheet1.Range["A1"].ColumnWidth = previousColumnWidth;
// Create a function for the conversion
Func<double, double> PointsToCharacters = (double points) => (points - PointsConstant ) / SizeOfLetter ;

Fit text to existing Surface in pygame

I'm trying to create text surfaces that are of the same size no matter the text. In other words: I want longer text to have smaller font size and shorter text to have bigger font size in order to fit the text to an already existing Surface.
To create text in pygame I am:
Creating a font object. For example: font = pygame.font.SysFont('Arial', 32)
Creating a text surface from the font object. For example: text = font.render('My text', True, (255, 255, 255))
Bliting the text surface.
The problem is that I first need to create a font object of a certain size before creating the text surface. I've created a function that does what I want:
import pygame
def get_text(surface, text, color=(255, 255, 255), max_size=128, font_name='Arial'):
"""
Returns a text surface that fits inside given surface. The text
will have a font size of 'max_size' or less.
"""
surface_width, surface_height = surface.get_size()
lower, upper = 0, max_size
while True:
font = pygame.font.SysFont(font_name, max_size)
font_width, font_height = font.size(text)
if upper - lower <= 1:
return font.render(text, True, color)
elif max_size < 1:
raise ValueError("Text can't fit in the given surface.")
elif font_width > surface_width or font_height > surface_height:
upper = max_size
max_size = (lower + upper) // 2
elif font_width < surface_width or font_height < surface_height:
lower = max_size
max_size = (lower + upper) // 2
else:
return font.render(text, True, color)
Is there any other way to solve this problem that's cleaner and/or more efficient?
This, unfortunately, seems to be the most appropriate solution. Font sizes are more of a approximation and differs between fonts, so there isn't a uniform way to calculate the area a specific font will take up. Another problem is that certain characters differs in size for certain fonts.
Having a monospace font would theoretically make it more efficient to calculate. Just by dividing the surface's width with the string's length and check which size of a monospace font covers that area.
You can re-scale the text image to fit:
def fit_text_to_width(text, color, pixels, font_face = None):
font = pygame.font.SysFont(font_face, pixels *3 // len(text) )
text_surface = font.render(text, True, color)
size = text_surface.get_size()
size = ( pixels, int(size[1] * pixels / size[0]) )
return pygame.transform.scale(text_surface, size)
The Font object seems to have to methods size() -> (width, height) and metrics() -> list[(minx, maxx, miny, maxy)] that sounds useful for checking the size of each resulting character and width of text.
The simplest way would be to use size and simply scale down text to the required width and height based on some ratio screen_width / font.size()[0] for example.
For breaking the text into lines, the metrics method is needed. It should be possible to loop through the metrics list and split up the text based on the summed width for each line.

rotation graphics in post script

I was wondering how can I rotate a graphic, say a rectangular by certain angle in post script.
Or at least is there any way to draw a very bold ! like, with an angle !?
I have list of sentence around a circle, so each or in 1 direction, and now, I would like to put each in a rectangular and make hyperlink for them.
The power of Postscript is its ruthless pursuit of the ideal of "delayed binding". The implementation of rotations is no exception. It works by making use of a more general tool, the Affine Transformation Matrix.
You can rotate both text and graphics (because text IS graphics) because all user specified coordinates are first multiplied through this matrix to produce device coordinates.
To perform all the necessary tricks (scaling, rotation, shear, translation), we first have to extend the 2d points to 3d points on the plane z=1 (don't ask me why; read Bill Casselman's Mathematical Illustrations or the Adobe Blue Book for more).
[ x [ a b 0
y * c d 0 = [ x' y' 1 ] = [ ax+cy+e bx+dy+f 1 ]
1 ] e f 1 ]
Since the 3rd column of the matrix is always [ 0 0 1 ] it is omitted from the external representation, and the matrix is described in postscript as:
[ a b c d e f ]
So when you use a coordinate pair for, say, a moveto operator, moveto first transforms it to device coordinates, x' = ax+by+e, y' = cx+dy+f, before adding a <</move [x' y']>> element to the current path.
Change the matrix: change the "meaning" of user coordinates.
The identity matrix is this:
[ 1 0 0 1 0 0 ] % x' = x, y' = y
To scale, replace the 1s with x and y scaling factors:
[ Sx 0 0 Sy 0 0 ] % x' = Sx*x, y' = Sy*y
To translate, replace e and f with the x and y translation offsets:
[ 1 0 0 1 Tx Ty ] % x' = x+Tx, y' = y+Ty
To rotate, replace a,b,c,d with sin and cos scaling and shearing factors:
[ cosW sinW -sinW cosW 0 0 ] % x' = x*cosW-y*sinW, y' = x*sinW+y*cosW, where W is angle(degrees) from x-axis
You "install" this matrix with concat which takes the Current Tranformation Matrix (CTM), multiplies it by your new matrix, and uses the product as the new CTM. So translate, rotate, and scale are just "convenience functions" which could be implemented like this:
/translate { [ 1 0 0 1 7 -2 roll ] concat } def
/scale { [ 3 1 roll 0 0 3 -1 roll 0 0 ] concat } def
/rotate { [ exch dup cos exch sin dup neg 2 index 0 0 ] concat } def
Since the CTM is part of the graphics state, you can use the graphics state stack to manipulate your transformations in a hierarchical manner:
/box { % x y w h %create a path in the shape of a box w*h with lower left corner at x,y
4 2 roll moveto
exch dup 3 1 roll
0 rlineto
0 exch rlineto
neg 0 rlineto
closepath
} def
/Courier 10 selectfont
100 100 100 100 box stroke % draw an oriented box
120 120 moveto (inside) show
gsave
150 150 translate % make the center of the box the new 0,0 point
45 rotate % rotate CCW 45 degrees
0 0 100 100 box stroke % a rotated, shifted box
20 20 moveto (inside) show
grestore
100 200 100 100 box stroke % another box, just north of the first, in the original coordinte system
120 220 moveto (inside) show
This produces the following image:
(source: googlecode.com)
I haven't used PostScript for a long time, but as I remember you could just use "rotate".
% do some steps
% ...
% ...
20 20 moveto % go to new position
30 /Times-Roman SetFont % select active font
45 rotate % set direction to diagonal
(Something)show % print text "Something"
showpage % show it all
cheers Kris
Postscript renders graphics in a given context - and it is this context that can be rotated (or scaled/translated) before drawing. Therefore any element on the image can be transformed as one wishes, all you have to do is to perform the necessary context transforms beforehand.
However, unfortunately, while I can give you an idea of it in this writing, i is a fundamental concept of Postscript, and you won't be able to do any real work in it without understanding that first. I suggest reading a brief tutorial such as the one in http://paulbourke.net/dataformats/postscript/ .
So, the "rotate" name is a function that does rotate the graphcis context - you use rotate, before drawing anything you want (rendering text also being "drawing" in this case).
%!PS
(Helvetica) findfont 12 scalefont setfont %select a font to use
300 300 translate % sets the orign at 300,300 points from the bottom left of page
/start 5 def % creates variable for keeping track of horizontal position of text
36 % pushes number of repeats on the stack
{
start 5 moveto % places cursor on the starting position
(postscript) show % renders the string in the starting position, within the current context
/start start 3 add def % increases the value on the variable
10 rotate % rotates the context 10 degrees clockwise (around the 300,300 new origin)
} repeat
showpage % renders whole page

How can you get the height metric of a string in PostScript?

You can obtain the width of a string in the current font with stringwidth and although this actually pushes offset coordinates on the stack, the y-value always seems to be useless. Is there a way to determine the exact height of a string, that may or may not include descenders?
stringwidth, as it says, doesn't return string's height. (In all cases I looked at, the second integer on the stack after executing stringwidth was 0 -- for strings that run in horizontal direction.) stringwidth gives the relative coordinates of the currentpoint after executing a (string) show.
The PLRM has this to say about stringwidth:
Note that the width returned by stringwidth is defined as movement of the current
point. It has nothing to do with the dimensions of the glyph outlines.
So what would work to take into account the string's height? The magic words to read up about in PRLM are charpath and pathbbox. Try this:
%!
/Helvetica findfont 60 scalefont setfont
200 700 4 0 360 arc fill
200 700 moveto (test test) dup
true charpath pathbbox
3 -1 roll sub 2 div neg 3 1 roll sub 2 div exch
1 0 0 setrgbcolor
200 700 moveto rmoveto show showpage
It calculates the string's (printed in red) height and uses that info to try and center a small filled circle (printed in black) into the center of its bounding box:
I have already answered this in How to determine string height in PostScript?, but it is useful here also.
Just adding to pipitas answer:
/textheight {
gsave % save graphic context
{
100 100 moveto % move to some point
(HÍpg) true charpath pathbbox % gets text path bounding box (LLx LLy URx URy)
exch pop 3 -1 roll pop % keeps LLy and URy
exch sub % URy - LLy
}
stopped % did the last block fail?
{
pop pop % get rid of "stopped" junk
currentfont /FontMatrix get 3 get % gets alternative text height
}
if
grestore % restore graphic context
} bind def
/jumpTextLine {
textheight 1.25 mul % gets textheight and adds 1/4
0 exch neg rmoveto % move down only in Y axis
} bind def
The method expects that some font is already set. It works over the selected font (setfont) and its size (scalefont).
I use (HÍpg) to get the biggest bounding box possible, using accentuated uppercase characters and "below line" characters. The result is good enough.
The alternative approach steals from dreamlax's answer -- some fonts do not support charpath operator.
Saving and restoring the graphic context keeps the current point in place, so it has no impact over the "flow" of your document.
Hope I've helped.
This seems to work most of the time:
/fontheight { currentfont /FontMatrix get 3 get } bind def
/lineheight { fontheight 1.2 mul } bind def
It won't work for all /FontTypes.

Resources