95 views

Skip to first unread message

Mar 29, 2014, 1:27:46 PM3/29/14

to

This question asks how a PostScript program can know how thick is a glyph.

What do I mean by "thick"?

Start with

http://www.jdawiseman.com/2013/20131011_champagne_ahb.pdf

On the first page observe the styling of the large letters ("MC", "H", "ED"). This is made with a charpath, a clip, and then repeated gsave stroke grestore: very thick black; slightly less thick white; less thick black; etc.

Works well. Looks good. Also see

http://www.jdawiseman.com/2014/20140402_g80.pdf

http://www.jdawiseman.com/2014/20140422_1966.pdf

How big does the first setlinewidth need to be, to cover the whole character? That is, how thick is the font?

Currently my code needs the user to choose this. But can it be chosen by the PostScript? Can the PostScript know, for any given glyph (or even at the level of a font), what is the greatest distance between an interior point and the edge?

For those interested, this question is about the InlineTitles parameters in

http://www.jdawiseman.com/papers/placemat/placemat.html#inlinetitles

Thank you.

What do I mean by "thick"?

Start with

http://www.jdawiseman.com/2013/20131011_champagne_ahb.pdf

On the first page observe the styling of the large letters ("MC", "H", "ED"). This is made with a charpath, a clip, and then repeated gsave stroke grestore: very thick black; slightly less thick white; less thick black; etc.

Works well. Looks good. Also see

http://www.jdawiseman.com/2014/20140402_g80.pdf

http://www.jdawiseman.com/2014/20140422_1966.pdf

How big does the first setlinewidth need to be, to cover the whole character? That is, how thick is the font?

Currently my code needs the user to choose this. But can it be chosen by the PostScript? Can the PostScript know, for any given glyph (or even at the level of a font), what is the greatest distance between an interior point and the edge?

For those interested, this question is about the InlineTitles parameters in

http://www.jdawiseman.com/papers/placemat/placemat.html#inlinetitles

Thank you.

Mar 29, 2014, 1:44:20 PM3/29/14

to

jdaw1 <jdawi...@gmail.com> writes:

> How big does the first setlinewidth need to be, to cover the whole

> character? That is, how thick is the font?

If I'm understanding you correctly at all, then one thing you /could/
> How big does the first setlinewidth need to be, to cover the whole

> character? That is, how thick is the font?

do, is use charpath to get the outline of a capital I, flattenpath to

turn that to line segments, sort the segments by height, and for the

segments that intersect the vertical midpoint, find the leftmost and

rightmost points. Hopefully there's a better solution coming from

somebody else though.

-- Mark

Mar 29, 2014, 4:07:11 PM3/29/14

to

That is at least three things: interesting; a kludge; and sometimes wrong.

The first two are obvious.

Third, part I. It might be that other glyphs contain wider strokes than the 'I'. For example, the bottom two corners in a 'W' can contain points further from the edge than happens in the middle of a straight pen stroke.

Third, part II. What if the 'I' bows outward. That is, what if the narrowest part of the vertical stroke is at the vertical middle? That would cause my code to do too few 'gsave stroke grestore's.

The first two are obvious.

Third, part I. It might be that other glyphs contain wider strokes than the 'I'. For example, the bottom two corners in a 'W' can contain points further from the edge than happens in the middle of a straight pen stroke.

Third, part II. What if the 'I' bows outward. That is, what if the narrowest part of the vertical stroke is at the vertical middle? That would cause my code to do too few 'gsave stroke grestore's.

Mar 31, 2014, 5:38:16 AM3/31/14

to

Here's a horrible way to do it, imperfectly.

There is a string to be 'inlined'. Compute its path. Compute bounding box. Divide the height by 64, and take a grid of points with that spacing. So if the string is long, there will be many more columns than rows. Test each point in the grid with infill, and keep in an array those points that are in the interior of the path.

Set WidthMin to be tiny. Set Width to be WidthMin 2 mul. Then strokepath. Forall the stored interior points, infill. If there are any unpainted points, keep only those points, /WidthMin Width def, and repeat. If there are no unpainted points, /WidthMax Width def, and henceforth /Width WidthMin WidthMax add 2 div def. Repeat until WidthMax sufficiently close to WidthMin.

This succeeds only if the grid of points is sufficiently dense. But infill is slow, and 64^2 infills, some done many times, would be very slow. Yuck!

There is a string to be 'inlined'. Compute its path. Compute bounding box. Divide the height by 64, and take a grid of points with that spacing. So if the string is long, there will be many more columns than rows. Test each point in the grid with infill, and keep in an array those points that are in the interior of the path.

Set WidthMin to be tiny. Set Width to be WidthMin 2 mul. Then strokepath. Forall the stored interior points, infill. If there are any unpainted points, keep only those points, /WidthMin Width def, and repeat. If there are no unpainted points, /WidthMax Width def, and henceforth /Width WidthMin WidthMax add 2 div def. Repeat until WidthMax sufficiently close to WidthMin.

This succeeds only if the grid of points is sufficiently dense. But infill is slow, and 64^2 infills, some done many times, would be very slow. Yuck!

Mar 31, 2014, 7:36:55 PM3/31/14

to

On testing, it isn't that slow. The string "GC60", in /Optima-Bold, with 128 rows of dots, and the number of columns being a modest multiple of this, using Distiller XI on a 2.7GHz Mac, takes about 1.3 seconds. That's not immediate, but not terrible.

Nonetheless, the algorithm is still horrible.

Nonetheless, the algorithm is still horrible.

Apr 6, 2014, 7:00:38 PM4/6/14

to

The 'answer', horrible algorithm and all, is now implemented as the function LineWidthThatCoversPath, which can be found in

http://www.jdawiseman.com/papers/placemat/placemat.ps

Share and enjoy.

http://www.jdawiseman.com/papers/placemat/placemat.ps

Share and enjoy.

Apr 7, 2015, 7:25:13 AM4/7/15

to

> On testing, it isn't that slow. The string "GC60", in /Optima-Bold, with 128 rows of dots, and the number of columns being a modest multiple of this, using Distiller XI on a 2.7GHz Mac, takes about 1.3 seconds. That's not immediate, but not terrible.

When I said that it "isn't that slow", that is a feature of insufficient testing. It's quick enough in Distiller, but painfully slow in Ghostscript, as has recently been reported.
http://bugs.ghostscript.com/show_bug.cgi?id=695906

My OP's report:

http://www.theportforum.com/viewtopic.php?t=175&start=913

Apr 7, 2015, 7:27:18 AM4/7/15

to

I'll also post the code of LineWidthThatCoversPath, in case it changes in a later version. But be careful with line breaks that you can copy but that weren't in my original code.

% TargetAccuracy LineWidthThatCoversPath WidthMax WidthMin

% Calculates, for the current path, the greatest distance from an interior point to the edge.

% WidthMin is known to be less than this distance. WidthMax will be greater.

% https://groups.google.com/forum/#!topic/comp.lang.postscript/86b7Sg8v7B0

/LineWidthThatCoversPath

{

DeBugLevel 25 le {(+LineWidthThatCoversPath) OutputToLog} if

20 dict begin

//PrinterEpsilon 2 copy lt {exch} if pop /TargetAccuracy exch def

% Target accuracy is split into two equal parts. The grid separation is half. And then the WidthMax-WidthMin separation is allowed the other half, and sometimes less than that.

PathBBox /ury exch def /urx exch def /lly exch def /llx exch def

urx llx sub //PrinterEpsilon gt ury lly sub //PrinterEpsilon gt and

{

% For an equilateral triangle the point furthest from the three corners is 2/3 of the height from the corners, which is Sqrt3/3 of the base.

/NumRows ury lly sub TargetAccuracy 0.75 mul div def % Error to be <= TargetAccuracy/2, which is 2/3 of row height. So row height = 0.75*TargetAccuracy.

/NumCols urx llx sub TargetAccuracy 1.5 mul Sqrt3 div div def % Error to be <= TargetAccuracy/2, which is Sqrt3/3 of the width. So width = TargetAccuracy * 1.5 / Sqrt3.

30000 NumRows ceiling 1 add NumCols ceiling 1 add mul div dup 1 gt % 30k => max stack of about 60k

{sqrt dup NumRows mul ceiling cvi 1 add /NumRows exch def NumCols mul ceiling cvi 1 add /NumCols exch def}

{pop /NumRows NumRows ceiling cvi 1 add def /NumCols NumCols ceiling cvi 1 add def}

ifelse % NumRows*NumCols small

/HalfGapRows ury lly sub NumRows 2 mul div def

/HalfGapCols urx llx sub NumCols 2 mul div def

/MaxResolutionError HalfGapRows dup 4 mul HalfGapCols dup mul exch div add def

DeBugLevel 15 le {mark ( LineWidthThatCoversPath: NumRows = ) NumRows (; NumCols = ) NumCols (; HalfGapRows = ) HalfGapRows (; HalfGapCols = ) HalfGapCols (; MaxResolutionError = ) MaxResolutionError ConcatenateToMark OutputToLog} if

count currentsystemparams /MaxOpStack 2 copy known {get} {pop pop 65535} ifelse exch sub /StackSpaceRemaining exch def

/PointsMostDistanceFromEdge

[

1 1 NumCols 2 mul 1 sub

{

/ColNum exch def /X ColNum HalfGapCols mul llx add def

1 2 NumRows 2 mul

{

/RowNum exch def /Y RowNum HalfGapRows mul lly add def

RowNum 4 mod 2 idiv ColNum 2 mod eq {X Y infill {X Y /StackSpaceRemaining StackSpaceRemaining 2 sub def} if StackSpaceRemaining 2 lt {exit} if} if

} for % Y

StackSpaceRemaining 2 lt {exit} if

} for % X

] def % /PointsMostDistanceFromEdge

StackSpaceRemaining 2 lt {(Warning: LineWidthThatCoversPath has incomplete calculation because there might be too little stack space. Increasing TargetAccuracy would help.) OutputToLog} if

DeBugLevel 15 le {mark ( LineWidthThatCoversPath: PointsMostDistanceFromEdge length = ) PointsMostDistanceFromEdge length ConcatenateToMark OutputToLog} if

/WidthMin null def /WidthMax null def

/Width ury lly sub 6 div PrinterEpsilon 2 copy lt {exch} if pop def

{

gsave

Width setlinewidth strokepath

/NewArrayLength 0 def

0 2 PointsMostDistanceFromEdge length 2 sub

{

dup PointsMostDistanceFromEdge exch get exch 1 add PointsMostDistanceFromEdge exch get 2 copy

infill not

{

PointsMostDistanceFromEdge NewArrayLength 1 add 3 -1 roll put PointsMostDistanceFromEdge NewArrayLength 3 -1 roll put

/NewArrayLength NewArrayLength 2 add def

} {pop pop} ifelse % infill not

} for % PointsMostDistanceFromEdge

NewArrayLength 0 gt {/WidthMin Width def /PointsMostDistanceFromEdge PointsMostDistanceFromEdge 0 NewArrayLength getinterval def} {/WidthMax Width def} ifelse

grestore

DeBugLevel 15 le {mark ( LineWidthThatCoversPath: WidthMin = ) WidthMin (; WidthMax = ) WidthMax (; PointsMostDistanceFromEdge length = ) PointsMostDistanceFromEdge length ConcatenateToMark OutputToLog} if

WidthMax null ne

{

WidthMin null ne

{

% Complicated exit conditions. Generally by the time this becomes close, PointsMostDistanceFromEdge is short, so a a few extra rounds are fast.

WidthMax WidthMin sub MaxResolutionError lt WidthMin TargetAccuracy div cvi WidthMax MaxResolutionError add TargetAccuracy div cvi eq and {exit} if

WidthMax WidthMin sub dup TargetAccuracy 64 div lt exch //PrinterEpsilon le or {exit} if

} {WidthMax TargetAccuracy 2 div le WidthMax PrinterEpsilon le or {/WidthMin 0 def exit} if} ifelse % WidthMin null ne

} if % WidthMax null ne

/Width WidthMax null eq {WidthMin 2 mul} {WidthMin null eq {WidthMax 2 div} {WidthMin WidthMax add 2 div} ifelse} ifelse def

} loop

WidthMax MaxResolutionError add WidthMin

} {//PrinterEpsilon 0} ifelse end % Non-zero area

DeBugLevel 25 le {(-LineWidthThatCoversPath) OutputToLog} if

} def % /LineWidthThatCoversPath

% TargetAccuracy LineWidthThatCoversPath WidthMax WidthMin

% Calculates, for the current path, the greatest distance from an interior point to the edge.

% WidthMin is known to be less than this distance. WidthMax will be greater.

% https://groups.google.com/forum/#!topic/comp.lang.postscript/86b7Sg8v7B0

/LineWidthThatCoversPath

{

DeBugLevel 25 le {(+LineWidthThatCoversPath) OutputToLog} if

20 dict begin

//PrinterEpsilon 2 copy lt {exch} if pop /TargetAccuracy exch def

% Target accuracy is split into two equal parts. The grid separation is half. And then the WidthMax-WidthMin separation is allowed the other half, and sometimes less than that.

PathBBox /ury exch def /urx exch def /lly exch def /llx exch def

urx llx sub //PrinterEpsilon gt ury lly sub //PrinterEpsilon gt and

{

% For an equilateral triangle the point furthest from the three corners is 2/3 of the height from the corners, which is Sqrt3/3 of the base.

/NumRows ury lly sub TargetAccuracy 0.75 mul div def % Error to be <= TargetAccuracy/2, which is 2/3 of row height. So row height = 0.75*TargetAccuracy.

/NumCols urx llx sub TargetAccuracy 1.5 mul Sqrt3 div div def % Error to be <= TargetAccuracy/2, which is Sqrt3/3 of the width. So width = TargetAccuracy * 1.5 / Sqrt3.

30000 NumRows ceiling 1 add NumCols ceiling 1 add mul div dup 1 gt % 30k => max stack of about 60k

{sqrt dup NumRows mul ceiling cvi 1 add /NumRows exch def NumCols mul ceiling cvi 1 add /NumCols exch def}

{pop /NumRows NumRows ceiling cvi 1 add def /NumCols NumCols ceiling cvi 1 add def}

ifelse % NumRows*NumCols small

/HalfGapRows ury lly sub NumRows 2 mul div def

/HalfGapCols urx llx sub NumCols 2 mul div def

/MaxResolutionError HalfGapRows dup 4 mul HalfGapCols dup mul exch div add def

DeBugLevel 15 le {mark ( LineWidthThatCoversPath: NumRows = ) NumRows (; NumCols = ) NumCols (; HalfGapRows = ) HalfGapRows (; HalfGapCols = ) HalfGapCols (; MaxResolutionError = ) MaxResolutionError ConcatenateToMark OutputToLog} if

count currentsystemparams /MaxOpStack 2 copy known {get} {pop pop 65535} ifelse exch sub /StackSpaceRemaining exch def

/PointsMostDistanceFromEdge

[

1 1 NumCols 2 mul 1 sub

{

/ColNum exch def /X ColNum HalfGapCols mul llx add def

1 2 NumRows 2 mul

{

/RowNum exch def /Y RowNum HalfGapRows mul lly add def

RowNum 4 mod 2 idiv ColNum 2 mod eq {X Y infill {X Y /StackSpaceRemaining StackSpaceRemaining 2 sub def} if StackSpaceRemaining 2 lt {exit} if} if

} for % Y

StackSpaceRemaining 2 lt {exit} if

} for % X

] def % /PointsMostDistanceFromEdge

StackSpaceRemaining 2 lt {(Warning: LineWidthThatCoversPath has incomplete calculation because there might be too little stack space. Increasing TargetAccuracy would help.) OutputToLog} if

DeBugLevel 15 le {mark ( LineWidthThatCoversPath: PointsMostDistanceFromEdge length = ) PointsMostDistanceFromEdge length ConcatenateToMark OutputToLog} if

/WidthMin null def /WidthMax null def

/Width ury lly sub 6 div PrinterEpsilon 2 copy lt {exch} if pop def

{

gsave

Width setlinewidth strokepath

/NewArrayLength 0 def

0 2 PointsMostDistanceFromEdge length 2 sub

{

dup PointsMostDistanceFromEdge exch get exch 1 add PointsMostDistanceFromEdge exch get 2 copy

infill not

{

PointsMostDistanceFromEdge NewArrayLength 1 add 3 -1 roll put PointsMostDistanceFromEdge NewArrayLength 3 -1 roll put

/NewArrayLength NewArrayLength 2 add def

} {pop pop} ifelse % infill not

} for % PointsMostDistanceFromEdge

NewArrayLength 0 gt {/WidthMin Width def /PointsMostDistanceFromEdge PointsMostDistanceFromEdge 0 NewArrayLength getinterval def} {/WidthMax Width def} ifelse

grestore

DeBugLevel 15 le {mark ( LineWidthThatCoversPath: WidthMin = ) WidthMin (; WidthMax = ) WidthMax (; PointsMostDistanceFromEdge length = ) PointsMostDistanceFromEdge length ConcatenateToMark OutputToLog} if

WidthMax null ne

{

WidthMin null ne

{

% Complicated exit conditions. Generally by the time this becomes close, PointsMostDistanceFromEdge is short, so a a few extra rounds are fast.

WidthMax WidthMin sub MaxResolutionError lt WidthMin TargetAccuracy div cvi WidthMax MaxResolutionError add TargetAccuracy div cvi eq and {exit} if

WidthMax WidthMin sub dup TargetAccuracy 64 div lt exch //PrinterEpsilon le or {exit} if

} {WidthMax TargetAccuracy 2 div le WidthMax PrinterEpsilon le or {/WidthMin 0 def exit} if} ifelse % WidthMin null ne

} if % WidthMax null ne

/Width WidthMax null eq {WidthMin 2 mul} {WidthMin null eq {WidthMax 2 div} {WidthMin WidthMax add 2 div} ifelse} ifelse def

} loop

WidthMax MaxResolutionError add WidthMin

} {//PrinterEpsilon 0} ifelse end % Non-zero area

DeBugLevel 25 le {(-LineWidthThatCoversPath) OutputToLog} if

} def % /LineWidthThatCoversPath

Reply all

Reply to author

Forward

0 new messages

Search

Clear search

Close search

Google apps

Main menu