diff --git a/src/pixie/fontformats/opentype.nim b/src/pixie/fontformats/opentype.nim index ea169b0..35a5bab 100644 --- a/src/pixie/fontformats/opentype.nim +++ b/src/pixie/fontformats/opentype.nim @@ -86,6 +86,7 @@ type nameID*: uint16 length*: uint16 offset*: uint16 + text*: string NameTable* = ref object format*: uint16 @@ -429,6 +430,23 @@ proc readVersion16Dot16(buf: string, offset: int): float32 = failUnsupported("invalid version format") majorDigit.float32 + minorDigit.float32 / 10 +proc convertUTF16(input: string): string = + ## Converts UTF16 Big Endian to UTF8 string. + var i = 0 + while i < input.len: + var u1 = input.readUInt16(i) + i += 2 + if u1 - 0xd800 >= 0x800: + result.add Rune(u1.int) + else: + var u2 = input.readUInt16(i) + i += 2 + if ((u1 and 0xfc00) == 0xd800) and ((u2 and 0xfc00) == 0xdc00): + result.add Rune((u1.uint32 shl 10) + u2.uint32 - 0x35fdc00) + else: + # Error, produce tofu character. + result.add "□" + proc parseCmapTable(buf: string, offset: int): CmapTable = var i = offset buf.eofCheck(i + 4) @@ -655,6 +673,14 @@ 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.encodingID in {1, 3}: + record.text = convertUTF16(record.text) + record.text = record.text + result.nameRecords.add(record) i += 12 proc parseOS2Table(buf: string, offset: int): OS2Table = @@ -2467,11 +2493,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 == 1: + 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 +2561,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..363f3d9 100644 --- a/src/pixie/fonts.nim +++ b/src/pixie/fonts.nim @@ -496,6 +496,10 @@ proc parseOtf*(buf: string): Typeface {.raises: [PixieError].} = proc parseTtf*(buf: string): Typeface {.raises: [PixieError].} = parseOtf(buf) +proc parseTtc*(buf: string): Typeface {.raises: [PixieError].} = + result = Typeface() + result.opentype = parseOpenTypeCollection(buf)[0] + proc parseSvgFont*(buf: string): Typeface {.raises: [PixieError].} = result = Typeface() result.svgFont = svgfont.parseSvgFont(buf) @@ -697,6 +701,8 @@ proc readTypeface*(filePath: string): Typeface {.raises: [PixieError].} = parseTtf(readFile(filePath)) of ".otf": parseOtf(readFile(filePath)) + of ".ttc": + parseTtc(readFile(filePath)) of ".svg": parseSvgFont(readFile(filePath)) else: diff --git a/tests/fonts/NotoSerifCJK-Regular.ttc b/tests/fonts/NotoSerifCJK-Regular.ttc new file mode 100644 index 0000000..54c1fee Binary files /dev/null and b/tests/fonts/NotoSerifCJK-Regular.ttc differ diff --git a/tests/test_fonts_ttc.nim b/tests/test_fonts_ttc.nim new file mode 100644 index 0000000..2f28090 --- /dev/null +++ b/tests/test_fonts_ttc.nim @@ -0,0 +1,51 @@ +import pixie, strformat, unicode, pixie/fontformats/opentype + +block: + var font = readFont("/Windows/Fonts/simsun.ttc") + font.size = 72 + let image = newImage(220, 100) + image.fill(rgba(255, 255, 255, 255)) + image.fillText(font, "大目鳥") + image.writeFile("ttc.png") + +block: + var fonts = parseOpenTypeCollection(readFile("/Windows/Fonts/simsun.ttc")) + for font in fonts: + echo font.fullName + +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: + echo file + var fonts = parseOpenTypeCollection(readFile(file)) + for i, font in fonts: + echo " ", i, ": ", font.fullName