From 50ca3807f77eec9c01dcb5107da9348026e83c6c Mon Sep 17 00:00:00 2001 From: Rasmus Andersson Date: Sat, 1 Sep 2018 12:57:55 -0700 Subject: [PATCH] Adds fontsample tool for rendering PDF and PNG font samples on macOS --- misc/fontsample/.gitignore | 2 + misc/fontsample/Makefile | 44 ++++++ misc/fontsample/README.md | 21 +++ misc/fontsample/fontsample | Bin 0 -> 24500 bytes misc/fontsample/fontsample.mm | 270 ++++++++++++++++++++++++++++++++++ 5 files changed, 337 insertions(+) create mode 100644 misc/fontsample/.gitignore create mode 100644 misc/fontsample/Makefile create mode 100644 misc/fontsample/README.md create mode 100755 misc/fontsample/fontsample create mode 100644 misc/fontsample/fontsample.mm diff --git a/misc/fontsample/.gitignore b/misc/fontsample/.gitignore new file mode 100644 index 000000000..6142305dc --- /dev/null +++ b/misc/fontsample/.gitignore @@ -0,0 +1,2 @@ +*.o +*.d diff --git a/misc/fontsample/Makefile b/misc/fontsample/Makefile new file mode 100644 index 000000000..063d7262d --- /dev/null +++ b/misc/fontsample/Makefile @@ -0,0 +1,44 @@ +sources = fontsample.mm + +CC = clang +CXX = clang +LD = clang + +XFLAGS := -Wall -g -fno-common +CFLAGS += -std=c11 +CXXFLAGS += -std=c++11 -stdlib=libc++ -fno-rtti -fno-exceptions +LDFLAGS += -lc++ + +#flags release and debug targets (e.g. make DEBUG=1) +ifeq ($(strip $(DEBUG)),1) + XFLAGS += -O0 +# else +# XFLAGS += -Os -DNDEBUG +endif + +libs := -lobjc +frameworks := -framework Foundation \ + -framework CoreText \ + -framework CoreServices \ + -framework CoreGraphics \ + -framework ImageIO + +c_flags = $(CFLAGS) $(XFLAGS) -MMD -fobjc-arc +cxx_flags = $(CXXFLAGS) $(XFLAGS) -MMD -fobjc-arc +ld_flags = $(LDFLAGS) $(libs) $(frameworks) + +objects := $(sources:%.c=%.o) +objects := $(objects:%.cc=%.o) +objects := $(objects:%.mm=%.o) + +fontsample: $(objects) + $(LD) $(ld_flags) -o $@ $^ + +%.o: %.mm + $(CXX) $(cxx_flags) -c $< + +clean: + rm -f *.o + +all: fontsample +.PHONY: all clean diff --git a/misc/fontsample/README.md b/misc/fontsample/README.md new file mode 100644 index 000000000..357b93e6f --- /dev/null +++ b/misc/fontsample/README.md @@ -0,0 +1,21 @@ +# fontsample + +A macOS-specific program for generating a PDF with text sample of a +specific font file. + +``` +$ make +$ ./fontsample -h +usage: ./fontsample [options] + +options: + -h, -help Show usage and exit. + -z, -size Font size to render. Defaults to 96. + -t, -text Text line to render. Defaults to "Rags78 **A**". + -o Write output to instead of default filename. + Defaults to .pdf. If the provided filename + ends with ".png" a PNG is written instead of a PDF. + + + Any font file that macOS can read. +``` diff --git a/misc/fontsample/fontsample b/misc/fontsample/fontsample new file mode 100755 index 0000000000000000000000000000000000000000..d405bf5110fdbffdf946867784e677235bb2c5ad GIT binary patch literal 24500 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 csUQ9b +#import +#import +#import + + +static const char* prog = "?"; + +static struct Options { + NSString* output = nil; + CGFloat size = 96; + NSString* text = @"Rags78 **A**"; +}options{}; + +static const char usagetemplate[] = "" +"usage: %s [options] \n" +"\n" +"options:\n" +" -h, -help Show usage and exit.\n" +" -z, -size Font size to render. Defaults to %g.\n" +" -t, -text Text line to render. Defaults to \"%s\".\n" +" -o Write output to instead of default filename.\n" +" Defaults to .pdf. If the provided filename\n" +" ends with \".png\" a PNG is written instead of a PDF.\n" +"\n" +"\n" +" Any font file that macOS can read.\n" +; + +void usage() { + printf(usagetemplate, prog, options.size, options.text.UTF8String); +} + + +// must call CFRelease on the returned pointer +CTFontRef loadFont(NSString* filename, CGFloat size) { + CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)filename, kCFURLPOSIXPathStyle, false); + + CGDataProviderRef dataProvider = CGDataProviderCreateWithURL(url); + if (!dataProvider) { + fprintf(stderr, "%s: failed to read file %s\n", prog, filename.UTF8String); + exit(1); + } + + CGFontRef cgf = CGFontCreateWithDataProvider(dataProvider); + if (!cgf) { + fprintf(stderr, "%s: failed to parse font %s\n", prog, filename.UTF8String); + exit(1); + } + + CTFontRef ctf = CTFontCreateWithGraphicsFont(cgf, size, nil, nil); + if (!ctf) { + fprintf(stderr, "%s: CTFontCreateWithGraphicsFont failed\n", prog); + exit(1); + } + + CFRelease(cgf); + CFRelease(dataProvider); + CFRelease(url); + return ctf; +} + + +CTLineRef createTextLine(CTFontRef font, NSString* text) { + NSDictionary* attr = @{ (NSString*)kCTFontAttributeName: (__bridge id)font }; + return CTLineCreateWithAttributedString((CFAttributedStringRef)[[NSAttributedString alloc] initWithString:text attributes:attr]); +} + + +void draw(CGContextRef ctx, + CTLineRef textLine, + CGFloat width, + CGFloat height, + CGFloat descent) +{ + // white background + CGContextSetRGBFillColor(ctx, 1.0, 1.0, 1.0, 1.0); + CGContextFillRect(ctx, {{0,0},{width,height}}); + + // draw text + CGContextSetTextPosition(ctx, 0, descent); + CTLineDraw(textLine, ctx); +} + + +void makePDF(CTLineRef textLine, + CGFloat width, + CGFloat height, + CGFloat descent, + NSString* filename) +{ + CFMutableDataRef consumerData = CFDataCreateMutable(kCFAllocatorDefault, 0); + CGDataConsumerRef contextConsumer = CGDataConsumerCreateWithCFData(consumerData); + assert(contextConsumer); + const CGRect mediaBox{{0,0},{width,height}}; + auto ctx = CGPDFContextCreate(contextConsumer, &mediaBox, nil); + assert(ctx); + CGPDFContextBeginPage(ctx, nil); + + draw(ctx, textLine, width, height, descent); + + // CGContextDrawPDFPage(ctx, page); + CGPDFContextEndPage(ctx); + CGPDFContextClose(ctx); + CGContextRelease(ctx); + [(__bridge NSData*)consumerData writeToFile:filename atomically:NO]; +} + + +BOOL writePNG(CGImageRef image, NSString *filename) { + CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:filename]; + CGImageDestinationRef destination = CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, NULL); + if (!destination) { + NSLog(@"Failed to create CGImageDestination for %@", filename); + return NO; + } + + CGImageDestinationAddImage(destination, image, nil); + + if (!CGImageDestinationFinalize(destination)) { + NSLog(@"Failed to write image to %@", filename); + CFRelease(destination); + return NO; + } + + CFRelease(destination); + return YES; +} + + +void makePNG(CTLineRef textLine, + CGFloat width, + CGFloat height, + CGFloat descent, + NSString* filename) +{ + size_t widthz = (size_t)ceilf(width); + size_t heightz = (size_t)ceilf(height); + + void* data = malloc(widthz * heightz * 4); + + // Create the context and fill it with white background + CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast; + CGContextRef ctx = CGBitmapContextCreate(data, widthz, heightz, 8, widthz*4, space, bitmapInfo); + CGColorSpaceRelease(space); + + draw(ctx, textLine, (CGFloat)widthz, (CGFloat)heightz, descent); + + CGImageRef imageRef = CGBitmapContextCreateImage(ctx); + writePNG(imageRef, filename); + + free(data); + CGImageRelease(imageRef); + CGContextRelease(ctx); +} + + +void pdfmake(NSString* fontfile) { + NSString* text = @"Rags78 **A**"; + + NSString* outfile = options.output; + if (outfile == nil) { + // default to fontfile.pdf + outfile = [fontfile.stringByDeletingPathExtension stringByAppendingPathExtension:@"pdf"]; + } + + // Create an attributed string with string and font information + CTFontRef font = loadFont(fontfile, options.size); + + CTLineRef textLine = createTextLine(font, text); + if (!textLine) { + fprintf(stderr, "%s: invalid sample text\n", prog); + exit(1); + } + + // get font metrics + CGFloat ascent, descent, leading; + CGFloat width = CTLineGetTypographicBounds(textLine, &ascent, &descent, &leading); + CGFloat height = ascent + descent; + + printf("write %s\n", outfile.UTF8String); + if ([outfile.pathExtension.lowercaseString isEqualToString:@"png"]) { + makePNG(textLine, width, height, descent, outfile); + } else { + makePDF(textLine, width, height, descent, outfile); + } + + CFRelease(textLine); + CFRelease(font); +} + + +void badarg(const char* msg, const char* arg) { + fprintf(stderr, "%s: %s %s\n", prog, msg, arg); + usage(); + exit(1); +} + + +const char* getargval(const char* arg, int argi, int argc, const char * argv[]) { + int i = argi + 1; + if (i == argc || strlen(argv[i]) == 0 || argv[i][0] == '-') { + fprintf(stderr, "%s: missing value for argument %s\n", prog, arg); + usage(); + exit(1); + } + return argv[i]; +} + + +NSMutableArray* parseargs(int argc, const char * argv[]) { + auto args = [NSMutableArray new]; + if (argc == 0) { + fprintf(stderr, "invalid arguments\n"); + exit(0); + } + prog = argv[0]; + for (int i = 1; i < argc; i++) { + auto arg = argv[i]; + if (strlen(arg) > 1 && arg[0] == '-') { + if (strcmp(arg, "-h") == 0 || strcmp(arg, "-help") == 0) { + usage(); + exit(0); + } + if (strcmp(arg, "-o") == 0) { + auto val = getargval(arg, i++, argc, argv); + options.output = [NSString stringWithUTF8String:val]; + + } else if (strcmp(arg, "-z") == 0 || strcmp(arg, "-size") == 0) { + auto val = getargval(arg, i++, argc, argv); + auto f = atof(val); + if (isnan(f) || f < 1) { + badarg("invalid number", val); + } + options.size = (CGFloat)f; + + } else if (strcmp(arg, "-t") == 0 || strcmp(arg, "-text") == 0) { + auto val = getargval(arg, i++, argc, argv); + options.text = [NSString stringWithUTF8String:val]; + + } else { + badarg("unknown flag", arg); + } + continue; + } + [args addObject:[NSString stringWithUTF8String:arg]]; + } + return args; +} + + +int main(int argc, const char * argv[]) { + @autoreleasepool { + auto fontfiles = parseargs(argc, argv); + + if (fontfiles.count < 1) { + fprintf(stderr, "%s: missing \n", prog); + usage(); + return 1; + } else if (fontfiles.count > 1) { + fprintf(stderr, "%s: extraneous argument after \n", prog); + usage(); + return 1; + } + + pdfmake(fontfiles[0]); + } + return 0; +}