diff --git a/pixie.nimble b/pixie.nimble index 8fdc8ac..499aa10 100644 --- a/pixie.nimble +++ b/pixie.nimble @@ -9,10 +9,11 @@ requires "nim >= 1.4.8" requires "vmath >= 1.1.4" requires "chroma >= 0.2.5" requires "zippy >= 0.9.7" -requires "flatty >= 0.2.4" +requires "flatty >= 0.3.0" requires "nimsimd >= 1.0.0" requires "bumpy >= 1.1.0" + task bindings, "Generate bindings": proc compile(libName: string, flags = "") = diff --git a/src/pixie/fontformats/opentype.nim b/src/pixie/fontformats/opentype.nim index ea169b0..5d0e9e1 100644 --- a/src/pixie/fontformats/opentype.nim +++ b/src/pixie/fontformats/opentype.nim @@ -1,5 +1,5 @@ -import flatty/binny, math, pixie/common, pixie/paths, sets, strutils, tables, - unicode, vmath +import flatty/binny, flatty/encode, math, pixie/common, pixie/paths, sets, + strutils, tables, unicode, vmath ## See https://docs.microsoft.com/en-us/typography/opentype/spec/ @@ -86,6 +86,7 @@ type nameID*: uint16 length*: uint16 offset*: uint16 + text*: string NameTable* = ref object format*: uint16 @@ -655,6 +656,17 @@ proc parseNameTable(buf: string, offset: int): NameTable = record.nameID = buf.readUint16(i + 6).swap() record.length = buf.readUint16(i + 8).swap() record.offset = buf.readUint16(i + 10).swap() + record.text = buf[ + (offset + result.stringOffset.int + record.offset.int) ..< + (offset + result.stringOffset.int + record.offset.int + record.length.int) + ] + if record.platformID == 3 and + record.encodingID == 1 and + record.languageID == 1033: + record.text = fromUTF16BE(record.text) + + record.text = record.text + result.nameRecords.add(record) i += 12 proc parseOS2Table(buf: string, offset: int): OS2Table = @@ -923,10 +935,19 @@ const cffStandardStrings = [ const TOP_DICT_META = { 0: "version", 1: "notice", - 1200: "copyright", 2: "fullName", 3: "familyName", 4: "weight", + 5: "fontBBox", + + 13: "uniqueId", + 14: "xuid", + 15: "charset", + 16: "encoding", + 17: "charStrings", + 18: "private", + + 1200: "copyright", 1201: "isFixedPitch", 1202: "italicAngle", 1203: "underlinePosition", @@ -934,14 +955,7 @@ const TOP_DICT_META = { 1205: "paintType", 1206: "charstringType", 1207: "fontMatrix", - 13: "uniqueId", - 5: "fontBBox", 1208: "strokeWidth", - 14: "xuid", - 15: "charset", - 16: "encoding", - 17: "charStrings", - 18: "private", 1230: "ros", 1231: "cidFontVersion", 1232: "cidFontRevision", @@ -2467,11 +2481,19 @@ proc isCCW*(opentype: OpenType): bool {.inline.} = ## CFF - true - counterclockwise opentype.cff == nil -proc parseOpenType*(buf: string): OpenType {.raises: [PixieError].} = +proc fullName*(opentype: OpenType): string = + ## Returns full name of the font if available. + if opentype.cff != nil: + return opentype.cff.topDict.fullName + for record in opentype.name.nameRecords: + if record.nameID == 6 and record.languageID == 1033: + return record.text + +proc parseOpenType*(buf: string, startLoc = 0): OpenType {.raises: [PixieError].} = result = OpenType() result.buf = buf - var i: int + var i: int = startLoc buf.eofCheck(i + 12) @@ -2527,5 +2549,32 @@ proc parseOpenType*(buf: string): OpenType {.raises: [PixieError].} = except KeyError as e: raise newException(PixieError, "Missing required font table: " & e.msg) +proc parseOpenTypeCollection*(buf: string): seq[OpenType] {.raises: [PixieError].} = + ## Reads a true/open type collection and returns seq of OpenType files. + var i: int + buf.eofCheck(i + 12) + + let tag = buf[0 ..< 4] + if tag != "ttcf": + failUnsupported("invalid ttc file") + + let + majorVersion = buf.readUint16(i + 4).swap() + minorVersion = buf.readUint16(i + 6).swap() + numFonts = buf.readUint32(i + 8).swap() + + if majorVersion notin {1, 2} and minorVersion != 0: + failUnsupported("ttc version") + + var tableDirectoryOffsets: seq[uint32] + i += 12 + for n in 0 ..< numFonts: + buf.eofCheck(i + 4) + tableDirectoryOffsets.add(buf.readUint32(i).swap()) + i += 4 + + for dir in tableDirectoryOffsets: + result.add(parseOpenType(buf, dir.int)) + when defined(release): {.pop.} diff --git a/src/pixie/fonts.nim b/src/pixie/fonts.nim index d0b1410..d6485d7 100644 --- a/src/pixie/fonts.nim +++ b/src/pixie/fonts.nim @@ -706,6 +706,21 @@ proc readTypeface*(filePath: string): Typeface {.raises: [PixieError].} = result.filePath = filePath +proc readTypefaces*(filePath: string): seq[Typeface] {.raises: [PixieError].} = + ## Loads a OpenType Collection (.ttc). + try: + for opentype in parseOpenTypeCollection(readFile(filePath)): + let typeface = Typeface() + typeface.opentype = opentype + result.add(typeface) + except IOError as e: + raise newException(PixieError, e.msg, e) + +proc name*(typeface: Typeface): string = + ## Returns the name of the font. + if typeface.opentype != nil: + return typeface.opentype.fullName + proc readFont*(filePath: string): Font {.raises: [PixieError].} = ## Loads a font from a file. newFont(readTypeface(filePath)) diff --git a/tests/fonts/PTSans.ttc b/tests/fonts/PTSans.ttc new file mode 100644 index 0000000..33d42c7 Binary files /dev/null and b/tests/fonts/PTSans.ttc differ diff --git a/tests/test_fonts.nim b/tests/test_fonts.nim index 00f33f3..0ffff65 100644 --- a/tests/test_fonts.nim +++ b/tests/test_fonts.nim @@ -1,4 +1,4 @@ -import pixie, pixie/fileformats/png, strformat, unicode +import pixie, pixie/fileformats/png, strformat, unicode, os proc wh(image: Image): Vec2 = ## Return with and height as a size vector. @@ -1196,3 +1196,54 @@ block: ) doDiff(image, "customlineheight") + +block: + var font = readTypefaces("tests/fonts/PTSans.ttc")[0].newFont + font.size = 72 + let image = newImage(200, 100) + image.fill(rgba(255, 255, 255, 255)) + image.fillText(font, "AbCd") + +block: + var typefaces = readTypefaces("tests/fonts/PTSans.ttc") + for i, typeface in typefaces: + echo i, ": ", typeface.name + +when defined(windows): + block: + let files = @[ + "/Windows/Fonts/batang.ttc", + "/Windows/Fonts/BIZ-UDGothicB.ttc", + "/Windows/Fonts/BIZ-UDGothicR.ttc", + "/Windows/Fonts/BIZ-UDMinchoM.ttc", + "/Windows/Fonts/cambria.ttc", + "/Windows/Fonts/gulim.ttc", + "/Windows/Fonts/meiryo.ttc", + "/Windows/Fonts/meiryob.ttc", + "/Windows/Fonts/mingliub.ttc", + "/Windows/Fonts/msgothic.ttc", + "/Windows/Fonts/msjh.ttc", + "/Windows/Fonts/msjhbd.ttc", + "/Windows/Fonts/msjhl.ttc", + "/Windows/Fonts/msmincho.ttc", + "/Windows/Fonts/msyh.ttc", + "/Windows/Fonts/msyhbd.ttc", + "/Windows/Fonts/msyhl.ttc", + "/Windows/Fonts/simsun.ttc", + "/Windows/Fonts/Sitka.ttc", + "/Windows/Fonts/SitkaB.ttc", + "/Windows/Fonts/SitkaI.ttc", + "/Windows/Fonts/SitkaZ.ttc", + "/Windows/Fonts/UDDigiKyokashoN-B.ttc", + "/Windows/Fonts/UDDigiKyokashoN-R.ttc", + "/Windows/Fonts/YuGothB.ttc", + "/Windows/Fonts/YuGothL.ttc", + "/Windows/Fonts/YuGothM.ttc", + "/Windows/Fonts/YuGothR.ttc", + ] + for file in files: + if fileExists(file): + echo file + var typefaces = readTypefaces(file) + for i, typeface in typefaces: + echo i, ": ", typeface.name