From 00037d85dea8dfee60e4da9fd8f8a4549c5ac8ec Mon Sep 17 00:00:00 2001 From: Dheatly23 <71598333+Dheatly23@users.noreply.github.com> Date: Mon, 19 Jun 2023 13:09:07 +0700 Subject: [PATCH 01/13] Adds Bold and Italic Variant --- src/monocraft.py | 98 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 65 insertions(+), 33 deletions(-) diff --git a/src/monocraft.py b/src/monocraft.py index a193995..043ae7e 100644 --- a/src/monocraft.py +++ b/src/monocraft.py @@ -33,49 +33,61 @@ charactersByCodepoint = {} def generateFont(): - monocraft = fontforge.font() - monocraft.fontname = "Monocraft" - monocraft.familyname = "Monocraft" - monocraft.fullname = "Monocraft" - monocraft.copyright = "Idrees Hassan, https://github.com/IdreesInc/Monocraft" - monocraft.encoding = "UnicodeFull" - monocraft.version = "3.0" - monocraft.weight = "Regular" - monocraft.ascent = PIXEL_SIZE * 8 - monocraft.descent = PIXEL_SIZE - monocraft.em = PIXEL_SIZE * 9 - monocraft.upos = -PIXEL_SIZE # Underline position - monocraft.addLookup("ligatures", "gsub_ligature", (), (("liga",(("dflt",("dflt")),("latn",("dflt")))),)) - monocraft.addLookupSubtable("ligatures", "ligatures-subtable") + fontList = [fontforge.font() for _ in range(3)] + for font in fontList: + font.fontname = "Monocraft" + font.familyname = "Monocraft" + font.fullname = "Monocraft" + font.copyright = "Idrees Hassan, https://github.com/IdreesInc/Monocraft" + font.encoding = "UnicodeFull" + font.version = "3.0" + font.weight = "Regular" + font.ascent = PIXEL_SIZE * 8 + font.descent = PIXEL_SIZE + font.em = PIXEL_SIZE * 9 + font.upos = -PIXEL_SIZE # Underline position + font.addLookup("ligatures", "gsub_ligature", (), (("liga",(("dflt",("dflt")),("latn",("dflt")))),)) + font.addLookupSubtable("ligatures", "ligatures-subtable") + + font = fontList[0] + font.os2_stylemap = font.macstyle = 0 + font = fontList[1] + font.fontname = "Monocraft-Bold" + font.fullname = "Monocraft Bold" + font.os2_stylemap = font.macstyle = 1 + font = fontList[2] + font.fontname = "Monocraft-Italic" + font.fullname = "Monocraft Italic" + font.os2_stylemap = font.macstyle = 2 + font.italicangle = -15 for character in characters: charactersByCodepoint[character["codepoint"]] = character - monocraft.createChar(character["codepoint"], character["name"]) - pen = monocraft[character["name"]].glyphPen() - top = 0 - drawn = character - image, kw = generateImage(character) - drawImage(image, pen, **kw) - monocraft[character["name"]].width = PIXEL_SIZE * 6 + createChar(fontList, character["codepoint"], character["name"], image, **kw) print(f"Generated {len(characters)} characters") outputDir = "../dist/" if not os.path.exists(outputDir): os.makedirs(outputDir) - monocraft.generate(outputDir + "Monocraft-no-ligatures.ttf") + fontList[0].generate(outputDir + "Monocraft-no-ligatures.ttf") + fontList[1].generate(outputDir + "Monocraft-bold-no-ligatures.ttf") + fontList[2].generate(outputDir + "Monocraft-italic-no-ligatures.ttf") for ligature in ligatures: - lig = monocraft.createChar(-1, ligature["name"]) - pen = monocraft[ligature["name"]].glyphPen() + print(ligature["name"]) image, kw = generateImage(ligature) - drawImage(image, pen, **kw) - monocraft[ligature["name"]].width = PIXEL_SIZE * len(ligature["sequence"]) * 6 - lig.addPosSub("ligatures-subtable", tuple(map(lambda codepoint: charactersByCodepoint[codepoint]["name"], ligature["sequence"]))) + createChar(fontList, -1, ligature["name"], image, width=PIXEL_SIZE * len(ligature["sequence"]) * 6, **kw) + for font in fontList: + font[ligature["name"]].addPosSub("ligatures-subtable", tuple(map(lambda codepoint: charactersByCodepoint[codepoint]["name"], ligature["sequence"]))) print(f"Generated {len(ligatures)} ligatures") - monocraft.generate(outputDir + "Monocraft.ttf") - monocraft.generate(outputDir + "Monocraft.otf") + fontList[0].generate(outputDir + "Monocraft.ttf") + fontList[0].generate(outputDir + "Monocraft.otf") + fontList[1].generate(outputDir + "Monocraft-bold.ttf") + fontList[1].generate(outputDir + "Monocraft-bold.otf") + fontList[2].generate(outputDir + "Monocraft-italic.ttf") + fontList[2].generate(outputDir + "Monocraft-italic.otf") def generateImage(character): image = PixelImage() @@ -119,12 +131,12 @@ def imageFromArray(arr, x=0, y=0): data=bytes(x for a in reversed(arr) for x in a), ) -def drawImage(image, pen, *, dx=0, dy=0): - for polygon in generatePolygons(image): +def drawPolygon(poly, pen): + for polygon in poly: start = True for x, y in polygon: - x = (x + dx) * PIXEL_SIZE - y = (y + dy) * PIXEL_SIZE + x = int(math.floor(x * PIXEL_SIZE)) + y = int(math.floor(y * PIXEL_SIZE)) if start: pen.moveTo(x, y) start = False @@ -132,5 +144,25 @@ def drawImage(image, pen, *, dx=0, dy=0): pen.lineTo(x, y) pen.closePath() +BOLD_WEIGHT = 48 +ITALIC_MAT = (1, 0, math.tan(math.radians(15)), 1, 0, 0) + +def createChar(fontList, code, name, image=None, *, width=None, dx=0, dy=0): + if image is not None: + poly = [[(x + dx, y + dy) for x, y in p] + for p in generatePolygons(image)] + for font in fontList: + char = font.createChar(code, name) + if image is None: + char.width = width if width is not None else PIXEL_SIZE * 6 + continue + + drawPolygon(poly, char.glyphPen()) + if font.macstyle & 1 != 0: + char.changeWeight(BOLD_WEIGHT, "CJK", 0, 1, "auto", True) + if font.macstyle & 2 != 0: + char.transform(ITALIC_MAT, ("round", )) + char.width = width if width is not None else PIXEL_SIZE * 6 + generateFont() generateExamples(characters, ligatures, charactersByCodepoint) From dc7fc217809ae55a6d60f8692502aeb3b92e5c83 Mon Sep 17 00:00:00 2001 From: Dheatly23 <71598333+Dheatly23@users.noreply.github.com> Date: Mon, 19 Jun 2023 14:45:26 +0700 Subject: [PATCH 02/13] Speedup on Bold Variant --- src/monocraft.py | 38 ++++++++++++++++++++++----- src/polygonizer.py | 65 +++++++++++++++++++++++----------------------- 2 files changed, 65 insertions(+), 38 deletions(-) diff --git a/src/monocraft.py b/src/monocraft.py index 043ae7e..3dcde50 100644 --- a/src/monocraft.py +++ b/src/monocraft.py @@ -20,7 +20,7 @@ from generate_diacritics import generateDiacritics from generate_examples import generateExamples from polygonizer import PixelImage, generatePolygons -from generate_continuous_ligatures import generate_continuous_ligatures +from generate_continuous_ligatures import generate_continuous_ligatures PIXEL_SIZE = 120 @@ -75,7 +75,6 @@ def generateFont(): fontList[1].generate(outputDir + "Monocraft-bold-no-ligatures.ttf") fontList[2].generate(outputDir + "Monocraft-italic-no-ligatures.ttf") for ligature in ligatures: - print(ligature["name"]) image, kw = generateImage(ligature) createChar(fontList, -1, ligature["name"], image, width=PIXEL_SIZE * len(ligature["sequence"]) * 6, **kw) for font in fontList: @@ -87,7 +86,7 @@ def generateFont(): fontList[1].generate(outputDir + "Monocraft-bold.ttf") fontList[1].generate(outputDir + "Monocraft-bold.otf") fontList[2].generate(outputDir + "Monocraft-italic.ttf") - fontList[2].generate(outputDir + "Monocraft-italic.otf") + #fontList[2].generate(outputDir + "Monocraft-italic.otf") def generateImage(character): image = PixelImage() @@ -144,7 +143,7 @@ def drawPolygon(poly, pen): pen.lineTo(x, y) pen.closePath() -BOLD_WEIGHT = 48 +BOLD_DIST = 0.2 ITALIC_MAT = (1, 0, math.tan(math.radians(15)), 1, 0, 0) def createChar(fontList, code, name, image=None, *, width=None, dx=0, dy=0): @@ -157,9 +156,36 @@ def createChar(fontList, code, name, image=None, *, width=None, dx=0, dy=0): char.width = width if width is not None else PIXEL_SIZE * 6 continue - drawPolygon(poly, char.glyphPen()) if font.macstyle & 1 != 0: - char.changeWeight(BOLD_WEIGHT, "CJK", 0, 1, "auto", True) + def f(p): + l = len(p) + for i, (x, y) in enumerate(p): + x_, y_ = x + dx, y + dy + px, py = p[i - 1] + if px < x: + y_ -= BOLD_DIST + elif px > x: + y_ += BOLD_DIST + elif py < y: + x_ += BOLD_DIST + else: + x_ -= BOLD_DIST + px, py = p[(i + 1) % l] + if px < x: + y_ += BOLD_DIST + elif px > x: + y_ -= BOLD_DIST + elif py < y: + x_ -= BOLD_DIST + else: + x_ += BOLD_DIST + yield (x_, y_) + drawPolygon( + [f(p) for p in generatePolygons(image, join_polygons=True)], + char.glyphPen(), + ) + else: + drawPolygon(poly, char.glyphPen()) if font.macstyle & 2 != 0: char.transform(ITALIC_MAT, ("round", )) char.width = width if width is not None else PIXEL_SIZE * 6 diff --git a/src/polygonizer.py b/src/polygonizer.py index e102414..2160bff 100644 --- a/src/polygonizer.py +++ b/src/polygonizer.py @@ -94,7 +94,7 @@ def __getitem__(self, key): def __setitem__(self, key, value): ''' Sets a pixel at (x, y). - + Do nothing if out of bounds. ''' x, y = key @@ -188,9 +188,9 @@ def __or__(self, other): return ret -def generatePolygons(image): +def generatePolygons(image, **kw): for segment, start_pos in segmentize(image): - yield from polygonizeSegment(segment, start_pos) + yield from polygonizeSegment(segment, start_pos, **kw) def segmentize(image): @@ -322,7 +322,7 @@ def reverse(self): return ret -def polygonizeSegment(image, start_pos): +def polygonizeSegment(image, start_pos, join_polygons=True): x, y = start_pos # Make sure position is top left @@ -560,36 +560,37 @@ def find(l, x): x, y = p # Try to join polygons - for i in range(len(poly) - 1, -1, -1): - inner_poly, points_ = poly[i] - - for v in points.values(): - if len(v) > 1 and i in v: - j = min(v) - break - else: - continue - - outer_poly, points__ = poly[j] + if join_polygons: + for i in range(len(poly) - 1, -1, -1): + inner_poly, points_ = poly[i] + + for v in points.values(): + if len(v) > 1 and i in v: + j = min(v) + break + else: + continue - for i_, p in enumerate(inner_poly): - j_ = points__.get(p) - if j_ is not None: - break - else: - raise RuntimeError( - f'Should not happened {inner_poly} {outer_poly}') - - outer_poly[j_:j_] = [*inner_poly[i_:], *inner_poly[:i_]] - points__.update( - zip( - reversed(outer_poly), - range(len(outer_poly) - 1, -1, -1), - )) + outer_poly, points__ = poly[j] - del poly[i] - for v in points.values(): - v.discard(i) + for i_, p in enumerate(inner_poly): + j_ = points__.get(p) + if j_ is not None: + break + else: + raise RuntimeError( + f'Should not happened {inner_poly} {outer_poly}') + + outer_poly[j_:j_] = [*inner_poly[i_:], *inner_poly[:i_]] + points__.update( + zip( + reversed(outer_poly), + range(len(outer_poly) - 1, -1, -1), + )) + + del poly[i] + for v in points.values(): + v.discard(i) # Emit polygons for i, _ in poly: From 5bbe9790dafae959a2fe933e5b784f839af37db1 Mon Sep 17 00:00:00 2001 From: Dheatly23 <71598333+Dheatly23@users.noreply.github.com> Date: Mon, 19 Jun 2023 20:24:45 +0700 Subject: [PATCH 03/13] Fix Bold Font --- src/monocraft.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monocraft.py b/src/monocraft.py index 3dcde50..0a444b7 100644 --- a/src/monocraft.py +++ b/src/monocraft.py @@ -181,7 +181,7 @@ def f(p): x_ += BOLD_DIST yield (x_, y_) drawPolygon( - [f(p) for p in generatePolygons(image, join_polygons=True)], + [f(p) for p in generatePolygons(image, join_polygons=False)], char.glyphPen(), ) else: From 5ec7862f57757e5248ab6fb49cd6a9f2e46acd0b Mon Sep 17 00:00:00 2001 From: Dheatly23 <71598333+Dheatly23@users.noreply.github.com> Date: Fri, 23 Jun 2023 20:52:16 +0700 Subject: [PATCH 04/13] Adds Bold Italic and Collect Fonts into .ttc --- src/monocraft.py | 77 +++++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/src/monocraft.py b/src/monocraft.py index 0a444b7..e3acd54 100644 --- a/src/monocraft.py +++ b/src/monocraft.py @@ -33,7 +33,7 @@ charactersByCodepoint = {} def generateFont(): - fontList = [fontforge.font() for _ in range(3)] + fontList = [fontforge.font() for _ in range(4)] for font in fontList: font.fontname = "Monocraft" font.familyname = "Monocraft" @@ -54,12 +54,19 @@ def generateFont(): font = fontList[1] font.fontname = "Monocraft-Bold" font.fullname = "Monocraft Bold" + font.weight = "Bold" font.os2_stylemap = font.macstyle = 1 font = fontList[2] font.fontname = "Monocraft-Italic" font.fullname = "Monocraft Italic" font.os2_stylemap = font.macstyle = 2 font.italicangle = -15 + font = fontList[2] + font.fontname = "Monocraft-BoldItalic" + font.fullname = "Monocraft Bold Italic" + font.weight = "Bold" + font.os2_stylemap = font.macstyle = 3 + font.italicangle = -15 for character in characters: charactersByCodepoint[character["codepoint"]] = character @@ -72,8 +79,13 @@ def generateFont(): os.makedirs(outputDir) fontList[0].generate(outputDir + "Monocraft-no-ligatures.ttf") - fontList[1].generate(outputDir + "Monocraft-bold-no-ligatures.ttf") - fontList[2].generate(outputDir + "Monocraft-italic-no-ligatures.ttf") + fontList[0].generateTtc( + outputDir + "Monocraft-no-ligatures.ttc", + fontList[1:], + ttcflags=("merge",), + layer=1, + ) + for ligature in ligatures: image, kw = generateImage(ligature) createChar(fontList, -1, ligature["name"], image, width=PIXEL_SIZE * len(ligature["sequence"]) * 6, **kw) @@ -83,10 +95,12 @@ def generateFont(): fontList[0].generate(outputDir + "Monocraft.ttf") fontList[0].generate(outputDir + "Monocraft.otf") - fontList[1].generate(outputDir + "Monocraft-bold.ttf") - fontList[1].generate(outputDir + "Monocraft-bold.otf") - fontList[2].generate(outputDir + "Monocraft-italic.ttf") - #fontList[2].generate(outputDir + "Monocraft-italic.otf") + fontList[0].generateTtc( + outputDir + "Monocraft.ttc", + fontList[1:], + ttcflags=("merge",), + layer=1, + ) def generateImage(character): image = PixelImage() @@ -156,32 +170,33 @@ def createChar(fontList, code, name, image=None, *, width=None, dx=0, dy=0): char.width = width if width is not None else PIXEL_SIZE * 6 continue + def boldify(p): + l = len(p) + for i, (x, y) in enumerate(p): + x_, y_ = x + dx, y + dy + px, py = p[i - 1] + if px < x: + y_ -= BOLD_DIST + elif px > x: + y_ += BOLD_DIST + elif py < y: + x_ += BOLD_DIST + else: + x_ -= BOLD_DIST + px, py = p[(i + 1) % l] + if px < x: + y_ += BOLD_DIST + elif px > x: + y_ -= BOLD_DIST + elif py < y: + x_ -= BOLD_DIST + else: + x_ += BOLD_DIST + yield (x_, y_) + if font.macstyle & 1 != 0: - def f(p): - l = len(p) - for i, (x, y) in enumerate(p): - x_, y_ = x + dx, y + dy - px, py = p[i - 1] - if px < x: - y_ -= BOLD_DIST - elif px > x: - y_ += BOLD_DIST - elif py < y: - x_ += BOLD_DIST - else: - x_ -= BOLD_DIST - px, py = p[(i + 1) % l] - if px < x: - y_ += BOLD_DIST - elif px > x: - y_ -= BOLD_DIST - elif py < y: - x_ -= BOLD_DIST - else: - x_ += BOLD_DIST - yield (x_, y_) drawPolygon( - [f(p) for p in generatePolygons(image, join_polygons=False)], + (boldify(p) for p in generatePolygons(image, join_polygons=False)), char.glyphPen(), ) else: From 1ee5a40d6eaf0433acda265b2b5705527fc7ec92 Mon Sep 17 00:00:00 2001 From: Dheatly23 <71598333+Dheatly23@users.noreply.github.com> Date: Fri, 23 Jun 2023 20:52:34 +0700 Subject: [PATCH 05/13] Reduce Boldness --- src/monocraft.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monocraft.py b/src/monocraft.py index e3acd54..0784da4 100644 --- a/src/monocraft.py +++ b/src/monocraft.py @@ -157,7 +157,7 @@ def drawPolygon(poly, pen): pen.lineTo(x, y) pen.closePath() -BOLD_DIST = 0.2 +BOLD_DIST = 0.1 ITALIC_MAT = (1, 0, math.tan(math.radians(15)), 1, 0, 0) def createChar(fontList, code, name, image=None, *, width=None, dx=0, dy=0): From b4294f637b8ded6a7c22ac2211210abe87e578fc Mon Sep 17 00:00:00 2001 From: Luna Date: Fri, 7 Jul 2023 18:08:49 +0300 Subject: [PATCH 06/13] Add html comment ligature --- examples/glyphs.txt | 1 + src/ligatures.json | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/examples/glyphs.txt b/examples/glyphs.txt index 0325540..a091504 100644 --- a/examples/glyphs.txt +++ b/examples/glyphs.txt @@ -51,6 +51,7 @@ a b c d e f g h i j k l m n o p q r s t u v w x y z  - > -> ->  = > -> =>  < = > -> <=> + < ! - - ->