359 lines
11 KiB
JavaScript
359 lines
11 KiB
JavaScript
/* This file was written by Glenn Maynard, and is in the public domain.
|
|
*
|
|
* This is a Photoshop javascript to generate Kanji fonts.
|
|
*
|
|
* You'll need the plugin:
|
|
*
|
|
* http://www.adobe.com/support/downloads/detail.jsp?ftpID=1536
|
|
*
|
|
* Load this script from File->Automation->Scripts, browse. Expect it to take
|
|
* a minute or two.
|
|
*
|
|
* Minimally, you'll need to edit the directories below.
|
|
*
|
|
* Each font page in FontPages is generated for each font in Fonts.
|
|
*
|
|
* I can't figure out a way to force a font to fixed-width, so this currently
|
|
* only works with fixed-width fonts. I'm using this primarily for Japanese
|
|
* fonts, so that's OK. If we could output Unicode, we could get around this,
|
|
* by outputting each glyph in a separate layer and aligning them individually,
|
|
* though that'd take a very long time.
|
|
*/
|
|
|
|
/* Okay, this is a stupid way to pass by reference, but I don't speak JavaScript ... */
|
|
function Integer(i) { this.val = i; }
|
|
|
|
var TemplateDir = "c:/temp/stepmania/utils/Font generation/Templates/";
|
|
var OutputDir = "c:/temp/stepmania/utils/Font generation/Output/";
|
|
|
|
/* We can't send Unicode into the font ourself for some reason. So, we have to
|
|
* store prototypes for each font page. These should just be PSD files that contain
|
|
* a single text layer with the characters we want. The orientation, font, etc.
|
|
* don't matter; they'll be overridden. */
|
|
var JAFontPages = Array("main", "kanji 1", "kanji 2","kanji 3","kanji 4");
|
|
var KRFontPages = Array("jamo 1", "jamo 2", "jamo 3", "jamo 4");
|
|
|
|
var Fonts = new Array(
|
|
/* name font pages frame sz font sz AA border bold */
|
|
new FontDef("_japanese 16px", "MS-Gothic", JAFontPages, 32, 20, AntiAlias.STRONG, 2, true),
|
|
new FontDef("_japanese 24px", "MS-Gothic", JAFontPages, 32, 26, AntiAlias.SMOOTH, 2, true)
|
|
//new FontDef("_korean 16px", "ArialUnicodeMS", KRFontPages, 32, 20, AntiAlias.SMOOTH, 2, true),
|
|
//new FontDef("_korean 24px", "ArialUnicodeMS", KRFontPages, 32, 28, AntiAlias.SMOOTH, 2, true)
|
|
);
|
|
|
|
|
|
var sel, doc;
|
|
|
|
var strtRulerUnits = preferences.rulerUnits;
|
|
var strtTypeUnits = preferences.typeUnits;
|
|
preferences.rulerUnits = Units.PIXELS;
|
|
preferences.typeUnits = TypeUnits.PIXELS;
|
|
displayDialogs = DialogModes.NO;
|
|
|
|
go();
|
|
|
|
preferences.rulerUnits = strtRulerUnits;
|
|
preferences.typeUnits = strtTypeUnits;
|
|
|
|
function FontDef(Name, /* filename prefix */
|
|
Font, /* actual font name */
|
|
Pages, /* list of font pages */
|
|
FrameSize, /* frame size in pixels */
|
|
FontSize, /* font character size in pixels (horiz) */
|
|
AAMethod, /* antialiasing */
|
|
Border, /* pixels or 0 for off */
|
|
Bold /* boolean */
|
|
)
|
|
{
|
|
this.Name = Name;
|
|
this.Font = Font;
|
|
this.Pages = Pages;
|
|
|
|
this.FrameSize = FrameSize;
|
|
/* This is the width of each character, as output by the font renderer.
|
|
* Changing this changes the size of the character. */
|
|
|
|
this.HorizSize = FontSize;
|
|
/* This is the actual vertical size of characters, as output by the font renderer.
|
|
* For MS Gothic, it always seems to be 4/3 of the vertical size. */
|
|
this.VertSize = FontSize* 4 / 3;
|
|
this.AAMethod = AAMethod;
|
|
this.Border = Border;
|
|
this.Bold = Bold;
|
|
}
|
|
|
|
function go()
|
|
{
|
|
for(var f = 0; f < Fonts.length; ++f)
|
|
{
|
|
for(var i = 0; i < Fonts[f].Pages.length; ++i)
|
|
{
|
|
MakeFontPage(TemplateDir + Fonts[f].Pages[i] + " template.psd",
|
|
OutputDir + Fonts[f].Name + " [" + Fonts[f].Pages[i] + "]",
|
|
Fonts[f]);
|
|
}
|
|
}
|
|
}
|
|
|
|
function MakeFontPage(TemplatePath, OutputPath, Font)
|
|
{
|
|
/* Open the template. */
|
|
var file = new File(TemplatePath);
|
|
if(!file.open("r"))
|
|
{
|
|
alert("Couldn't open " + TemplatePath);
|
|
return;
|
|
}
|
|
open(file);
|
|
file = null;
|
|
|
|
/* Change the template to a font page. */
|
|
var frameX = new Integer;
|
|
var frameY = new Integer;
|
|
ConvertCurrentDocument(frameX, frameY, Font);
|
|
|
|
/* Save the template to the destination file and close the template. */
|
|
var Save = new PNGSaveOptions;
|
|
Save.interlaced = false;
|
|
OutputPath += " " + frameX.val + "x" + frameY.val;
|
|
OutputPath += ".png";
|
|
activeDocument.saveAs(new File(OutputPath), Save, true, Extension.LOWERCASE);
|
|
activeDocument.close(SaveOptions.DONOTSAVECHANGES);
|
|
}
|
|
|
|
function GetTextLayer(Doc)
|
|
{
|
|
/* Find a text layer. */
|
|
var TextLayer = null;
|
|
for(var i = 0; i < activeDocument.artLayers.length; ++i)
|
|
{
|
|
if(activeDocument.artLayers[i].kind != LayerKind.TEXT)
|
|
continue; /* irrelevant */
|
|
|
|
if(TextLayer != null)
|
|
{
|
|
alert("More than one text layer found. Confused.");
|
|
return null;
|
|
}
|
|
|
|
TextLayer = activeDocument.artLayers[i];
|
|
}
|
|
|
|
if(TextLayer == null)
|
|
{
|
|
alert("No text layer found. Confused.");
|
|
return null;
|
|
}
|
|
|
|
return TextLayer;
|
|
}
|
|
|
|
/* We have an open document with a single text layer. Convert it to a font
|
|
* page. The result will be a single RGB layer. */
|
|
function ConvertCurrentDocument( frameX, frameY, Font )
|
|
{
|
|
// Create a new art layer and convert it to a text layer.
|
|
// Set its contents, size and color.
|
|
if (documents.length == 0)
|
|
{
|
|
alert ("You must have at least one open document to run this script.");
|
|
return;
|
|
}
|
|
|
|
doc = activeDocument;
|
|
sel = doc.selection;
|
|
|
|
/* Find a text layer. */
|
|
var TextLayer = GetTextLayer(doc);
|
|
if(TextLayer == null) return;
|
|
|
|
/* Figure out the number of frames in each dimension, based on the line count
|
|
* and max characters/line. */
|
|
GetFrameCount(TextLayer.textItem.contents, frameX, frameY);
|
|
|
|
/* Based on the frame count and frame size, figure out the dimensions of
|
|
* the whole page. */
|
|
var PageWidth = frameX.val * Font.FrameSize;
|
|
var PageHeight = frameY.val * Font.FrameSize;
|
|
|
|
/* Resize the image down to the size required to hold the images. This prevents
|
|
* errors later when we move columns around if the image is too big. */
|
|
doc.resizeCanvas(PageWidth, PageHeight, AnchorPosition.TOPLEFT);
|
|
|
|
{
|
|
/* Using HorizSize as the vertical origin seems to align the text so that
|
|
* it's centered in the top VertSize pixels. Adjust from there to being centered
|
|
* in the frame.
|
|
*
|
|
* Add half of a pixel. This seems to help alignment a lot. */
|
|
var Dist = Font.FrameSize/2-Font.VertSize/2;
|
|
TextLayer.textItem.position = Array(0, Font.HorizSize+Dist+.5);
|
|
}
|
|
|
|
TextLayer.textItem.font = Font.Font;
|
|
TextLayer.textItem.antiAliasMethod = Font.AAMethod;
|
|
var textColor = new SolidColor;
|
|
textColor.rgb.red = 255;
|
|
textColor.rgb.green = 255;
|
|
textColor.rgb.blue = 255;
|
|
|
|
TextLayer.textItem.color = textColor;
|
|
|
|
TextLayer.textItem.useAutoLeading = 0;
|
|
TextLayer.textItem.spaceBefore = Font.FrameSize;
|
|
TextLayer.textItem.spaceAfter = 0;
|
|
|
|
TextLayer.textItem.size = Font.HorizSize;
|
|
|
|
TextLayer.textItem.fauxBold = Font.Bold;
|
|
TextLayer.textItem.fauxItalic = false;
|
|
|
|
TextLayer.rasterize(RasterizeType.ENTIRELAYER);
|
|
|
|
var CurrentSpacing = Font.HorizSize;
|
|
/* Faux bold adds half a pixel to the width of each character. */
|
|
if(Font.Bold)
|
|
CurrentSpacing += 0.5;
|
|
|
|
/* We currently have a horizontal spacing of CurrentSpacing; adjust it to
|
|
* have a vertical spacing of FrameSize. */
|
|
Adjust(Font.FrameSize, CurrentSpacing, frameX.val);
|
|
|
|
if(Font.Border != 0)
|
|
TextLayer = MakeBorder(TextLayer, doc.artLayers, Font.Border);
|
|
|
|
{
|
|
/* Resize the texture to have the largest possible frame count without going
|
|
* over a power of two. This way, when the font page grows, the frame count
|
|
* (8x8) stays constant, so we don't have to keep renaming files in CVS. */
|
|
var MaxX = power_of_two(PageWidth);
|
|
var MaxY = power_of_two(PageHeight);
|
|
while(PageWidth + Font.FrameSize <= MaxX)
|
|
PageWidth += Font.FrameSize;
|
|
while(PageHeight + Font.FrameSize <= MaxY)
|
|
PageHeight += Font.FrameSize;
|
|
|
|
/* Actually, let's force the texture to be square, too, which reduces filename
|
|
* changes even more. This eats a little texture memory. It also means that
|
|
* if the texture is reduced at runtime, it'll be reduced equally in both
|
|
* dimensions, which will probably look less weird. */
|
|
if(PageHeight < PageWidth) PageHeight = PageWidth;
|
|
else PageWidth = PageHeight;
|
|
|
|
frameX.val = PageWidth / Font.FrameSize;
|
|
frameY.val = PageHeight / Font.FrameSize;
|
|
}
|
|
doc.resizeCanvas(PageWidth, PageHeight, AnchorPosition.TOPLEFT);
|
|
}
|
|
|
|
function GetFrameCount(str, x, y)
|
|
{
|
|
/* Strip blank lines off the end. */
|
|
for(var c = str.length-1; c >= 0; --c)
|
|
{
|
|
if(c != '\r' && c != ' ')
|
|
break;
|
|
str = str.substr(0, str.length-1);
|
|
}
|
|
|
|
var NumLines = 0, MaxLength = 0, CurLength = 0;
|
|
|
|
for(var c = 0; c < str.length; ++c)
|
|
{
|
|
if(str[c] == '\r')
|
|
{
|
|
CurLength = 0;
|
|
NumLines++;
|
|
}
|
|
else
|
|
{
|
|
CurLength++;
|
|
if(CurLength > MaxLength)
|
|
MaxLength = CurLength;
|
|
}
|
|
}
|
|
if(CurLength)
|
|
NumLines++;
|
|
|
|
x.val = MaxLength;
|
|
y.val = NumLines;
|
|
}
|
|
|
|
function power_of_two(n)
|
|
{
|
|
var value = 1;
|
|
|
|
while (value < n)
|
|
value *= 2;
|
|
|
|
return value;
|
|
}
|
|
|
|
/* The current document is in columns of width NewWidth. Space it out so the columns
|
|
* are of width OldWidth. */
|
|
function Adjust(NewWidth, OldWidth, Columns)
|
|
{
|
|
// col = 7;
|
|
for(var col = Columns-1; col >= 0; --col)
|
|
{
|
|
var selBounds = Array(
|
|
Array(col*OldWidth,0),
|
|
Array((col+1)*OldWidth, 0),
|
|
Array((col+1)*OldWidth, activeDocument.height),
|
|
Array(col*OldWidth, activeDocument.height));
|
|
sel.select(selBounds);
|
|
|
|
var OldXCenter = (col + .5) * OldWidth;
|
|
var NewXCenter = (col + .5) * NewWidth;
|
|
var Distance = NewXCenter-OldXCenter;
|
|
sel.translate(Distance);
|
|
}
|
|
sel.deselect();
|
|
}
|
|
|
|
function SelectTransparencyFromCurrentLayer()
|
|
{
|
|
// Voodoo from the action recorder, since I couldn't find a way to
|
|
// do this normally.
|
|
var id15 = charIDToTypeID( "setd" );
|
|
var desc3 = new ActionDescriptor();
|
|
var id16 = charIDToTypeID( "null" );
|
|
var ref1 = new ActionReference();
|
|
var id17 = charIDToTypeID( "Chnl" );
|
|
var id18 = charIDToTypeID( "fsel" );
|
|
ref1.putProperty( id17, id18 );
|
|
desc3.putReference( id16, ref1 );
|
|
var id19 = charIDToTypeID( "T " );
|
|
var ref2 = new ActionReference();
|
|
var id20 = charIDToTypeID( "Chnl" );
|
|
var id21 = charIDToTypeID( "Chnl" );
|
|
var id22 = charIDToTypeID( "Trsp" );
|
|
ref2.putEnumerated( id20, id21, id22 );
|
|
desc3.putReference( id19, ref2 );
|
|
executeAction( id15, desc3, DialogModes.NO );
|
|
}
|
|
|
|
/* Add a border to the current layer. */
|
|
function MakeBorder(Layer, Layers, Width)
|
|
{
|
|
SelectTransparencyFromCurrentLayer();
|
|
sel.expand(Width);
|
|
|
|
var BorderLayer = Layers.add();
|
|
BorderLayer.moveAfter(Layer);
|
|
|
|
var borderColor = new SolidColor;
|
|
borderColor.rgb.red = 0;
|
|
borderColor.rgb.green = 0;
|
|
borderColor.rgb.blue = 0;
|
|
|
|
sel.fill(borderColor);
|
|
sel.deselect();
|
|
|
|
Layer.merge();
|
|
|
|
return BorderLayer;
|
|
}
|
|
|
|
|