Skip to content

Commit

Permalink
Changes for v. 0.2.5 (see README.md)
Browse files Browse the repository at this point in the history
  • Loading branch information
psb1558 committed Feb 23, 2024
1 parent b7e3745 commit c4cc438
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 21 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ For more information, see the [documentation](https://github.com/psb1558/ygt/tre

## Changes

### Version 0.2.5 (coming soon)

Added monochrome (non-antialiased) render mode for preview panels.

Fixed a bug in entry of OT feature indices.

Fixed two bugs when switching between axes

### Version 0.2.4 (2023-5-30)

Qt was adding extra antialiasing in string/array preview. We now display only FreeType antialiasing.
Expand Down
Binary file modified docs/YGT-intro.odt
Binary file not shown.
Binary file modified docs/YGT-intro.pdf
Binary file not shown.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "ygt"
version = "0.2.4"
version = "0.2.5"
authors = [
{ name="Peter S. Baker", email="[email protected]" },
]
Expand Down
2 changes: 1 addition & 1 deletion src/ygt/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .window import main

if __name__ == '__main__':
if __name__ == "__main__":
main()
104 changes: 99 additions & 5 deletions src/ygt/freetypeFont.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
from PyQt6.QtCore import QLine

RENDER_GRAYSCALE = 1
RENDER_LCD_1 = 2
RENDER_LCD_2 = 3
RENDER_LCD_1 = 2
RENDER_LCD_2 = 3
RENDER_MONO = 4


class ygLetterBox:
Expand Down Expand Up @@ -123,6 +124,8 @@ def set_render_mode(self, render_mode):
self.draw_char = self._draw_char_lcd
elif self.render_mode == RENDER_LCD_2:
self.draw_char = self._draw_char_lcd
elif self.render_mode == RENDER_MONO:
self.draw_char = self._draw_char_mono
else:
self.draw_char = self._draw_char_grayscale

Expand Down Expand Up @@ -160,7 +163,9 @@ def set_char(self, glyph_index):
"""
self.glyph_index = glyph_index
flags = 4 # i.e. grayscale
if self.render_mode in [RENDER_LCD_1, RENDER_LCD_2]:
if self.render_mode == RENDER_MONO:
flags = ft.FT_LOAD_RENDER | ft.FT_LOAD_TARGET_MONO
elif self.render_mode in [RENDER_LCD_1, RENDER_LCD_2]:
flags = ft.FT_LOAD_RENDER | ft.FT_LOAD_TARGET_LCD
if not self.hinting_on:
flags = flags | ft.FT_LOAD_NO_HINTING | ft.FT_LOAD_NO_AUTOHINT
Expand All @@ -180,16 +185,44 @@ def _get_bitmap_metrics(self):
r["bitmap_left"] = self.glyph_slot.bitmap_left
r["advance"] = round(self.glyph_slot.advance.x / 64)
return r

#def glyph_bit(self, x, y):
# pitch = abs(self.glyph_slot.bitmap.pitch)
# row = self.glyph_slot.bitmap.buffer[pitch * y::]
# cValue = row[x >> 3]
# return cValue & (128 >> (x & 7)) != 0

def monomap_to_array(self, bitmap):
data = [0] * (bitmap.rows * bitmap.width)
for y in range(bitmap.rows):
for byte_index in range(bitmap.pitch):
byte_value = bitmap.buffer[y * bitmap.pitch + byte_index]
rowstart = y * bitmap.width + byte_index * 8
num_bits_done = byte_index * 8
bits = 8
if ((bitmap.width - num_bits_done) < 8):
bits = bitmap.width - num_bits_done
for bit_index in range(bits):
bit = byte_value & (1 << (7 - bit_index))
if bit:
data[rowstart + bit_index] = 1
return list(data)

def mk_array(self, metrics, render_mode):
data = []
rows = metrics["rows"]
width = metrics["width"]
pitch = metrics["pitch"]
if render_mode == RENDER_MONO:
newmap = self.monomap_to_array(self.glyph_slot.bitmap)
else:
newmap = self.glyph_slot.bitmap.buffer
for i in range(rows):
data.extend(self.glyph_slot.bitmap.buffer[i * pitch : i * pitch + width])
data.extend(newmap[i * pitch : i * pitch + width])
if render_mode == RENDER_GRAYSCALE:
return numpy.array(data, dtype=numpy.ubyte).reshape(rows, width)
elif render_mode == RENDER_MONO:
return newmap
else:
return numpy.array(data, dtype=numpy.ubyte).reshape(rows, int(width / 3), 3)

Expand Down Expand Up @@ -285,6 +318,67 @@ def _draw_char_lcd(
)
)
return gdata["advance"]


def _draw_char_mono(
self,
painter,
x,
y,
spacing_mark=False,
dark_theme = False,
is_target = False,
x_offset = 0,
y_offset = 0,
):
gdata = self._get_bitmap_metrics()
bm = self.mk_array(gdata, self.render_mode)
ypos = (y - gdata["bitmap_top"]) - y_offset
starting_ypos = ypos
is_mark = spacing_mark and (gdata["advance"] == 0)
if is_mark:
starting_xpos = xpos = x
xpos += 2
gdata["advance"] = self.advance = gdata["width"] + 4
else:
starting_xpos = xpos = (x + gdata["bitmap_left"]) + x_offset
qp = QPen(QColor("black"))
qp.setWidth(1)
on_pixel = QColor("white") if dark_theme else QColor("black")
qp.setColor(QColor(on_pixel))
painter.setPen(qp)
bytestepper = 0
for r in range(gdata["rows"]):
xpos = starting_xpos
for w in range(gdata["width"]):
if bm[bytestepper]:
painter.drawPoint(xpos, ypos)
bytestepper += 1
xpos += 1
ypos += 1
ending_xpos = starting_xpos + round(gdata["advance"])
ending_ypos = starting_ypos + gdata["rows"]
if is_target:
ul_y = ending_ypos + 4
qc = QPen(QColor("red"))
qc.setWidth(2)
painter.setPen(qc)
painter.drawLine(QLine(starting_xpos, ul_y, ending_xpos, ul_y))
if abs(ending_ypos - starting_ypos) <= 5:
starting_ypos -= 3
ending_ypos += 3
self.rect_list.append(
ygLetterBox(
starting_xpos,
starting_ypos,
ending_xpos,
ending_ypos,
glyph_index=self.glyph_index,
size=self.size,
gname=self.index_to_name(self.glyph_index),
)
)
return gdata["advance"]


def _draw_char_grayscale(
Expand All @@ -310,7 +404,7 @@ def _draw_char_grayscale(
"""
gdata = self._get_bitmap_metrics()
Z = self.mk_array(gdata, RENDER_GRAYSCALE)
Z = self.mk_array(gdata, self.render_mode)
ypos = (y - gdata["bitmap_top"]) - y_offset
starting_ypos = ypos
is_mark = spacing_mark and (gdata["advance"] == 0)
Expand Down
6 changes: 5 additions & 1 deletion src/ygt/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
# FileNameVar = TypeVar("FileNameVar", str, tuple[str, Any])
FileNameVar = Union[str, tuple[str, Any]]
# FileNameVar = Any
ygt_version = "0.2.4"
ygt_version = "0.2.5"


class ygPreviewFontMaker(QThread):
Expand Down Expand Up @@ -422,14 +422,17 @@ def __init__(
self.pv_mode_1_action = self.pv_render_mode_menu.addAction("Grayscale")
self.pv_mode_2_action = self.pv_render_mode_menu.addAction("Subpixel (1)")
self.pv_mode_3_action = self.pv_render_mode_menu.addAction("Subpixel (2)")
self.pv_mode_4_action = self.pv_render_mode_menu.addAction("Monochrome")
self.render_action_group = QActionGroup(self.pv_render_mode_menu)
self.render_action_group.addAction(self.pv_mode_1_action)
self.render_action_group.addAction(self.pv_mode_2_action)
self.render_action_group.addAction(self.pv_mode_3_action)
self.render_action_group.addAction(self.pv_mode_4_action)
self.pv_mode_1_action.setCheckable(True)
self.pv_mode_2_action.setCheckable(True)
self.pv_mode_2_action.setChecked(True)
self.pv_mode_3_action.setCheckable(True)
self.pv_mode_4_action.setCheckable(True)
self.pv_render_mode_menu.setEnabled(False)

self.preview_menu.addSeparator()
Expand Down Expand Up @@ -1016,6 +1019,7 @@ def setup_preview_connections(self) -> None:
self.pv_mode_1_action.triggered.connect(self.yg_preview.render1)
self.pv_mode_2_action.triggered.connect(self.yg_preview.render2)
self.pv_mode_3_action.triggered.connect(self.yg_preview.render3)
self.pv_mode_4_action.triggered.connect(self.yg_preview.render4)
self.pv_theme_auto_action.triggered.connect(self.yg_preview.set_theme_auto)
self.pv_theme_light_action.triggered.connect(self.yg_preview.set_theme_light)
self.pv_theme_dark_action.triggered.connect(self.yg_preview.set_theme_dark)
Expand Down
15 changes: 10 additions & 5 deletions src/ygt/ygHintEditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3161,12 +3161,16 @@ def switch_to(self, gname: str, caller: Optional[str] = None) -> None:
if gname in self.visited_glyphs:
self.yg_glyph_scene = self.visited_glyphs[gname]
new_glyph = self.yg_glyph_scene.yg_glyph
#new line:
new_glyph.axis = self.preferences.top_window().current_axis
# If we're returning to a glyph, we have to undo the cleanup
# we did when we left it.
new_glyph.undo_stack.setActive()
new_glyph.restore_gsource()
else:
new_glyph = ygGlyph(self.preferences, self.yg_font, gname)
#new line:
new_glyph.axis = self.preferences.top_window().current_axis
self.yg_glyph_scene = ygGlyphScene(self.preferences, new_glyph, owner = self)
self.preferences.set_current_glyph(self.yg_font.full_name, gname)
self.setScene(self.yg_glyph_scene)
Expand All @@ -3191,18 +3195,19 @@ def guess_cv(self) -> None:
def switch_to_x(self, checked: bool) -> None:
if self.yg_glyph_scene:
if checked and self.yg_glyph_scene.yg_glyph.axis != "x":
# self.yg_glyph_scene.axis = "x"
self.yg_glyph_scene.yg_glyph.switch_to_axis("x")
win = self.parent().parent()
win.set_window_title() # type: ignore
#win = self.parent().parent()
self.parent().parent().set_window_title() # type: ignore


@pyqtSlot(bool)
def switch_to_y(self, checked: bool) -> None:
if self.yg_glyph_scene:
if checked and self.yg_glyph_scene.yg_glyph.axis != "y":
self.yg_glyph_scene.yg_glyph.switch_to_axis("y")
win = self.parent().parent()
win.set_window_title() # type: ignore
#win = self.parent().parent()
#win.set_window_title() # type: ignore
self.parent().parent().set_window_title() # type: ignore

@pyqtSlot()
def cleanup_yaml_code(self) -> None:
Expand Down
16 changes: 11 additions & 5 deletions src/ygt/ygModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2038,13 +2038,18 @@ def redo(self) -> None:
if self.yg_glyph.axis == self.new_axis:
return
self.top_window.current_axis = self.yg_glyph.axis = self.new_axis
self.yg_glyph._hints_changed(self.yg_glyph.hints, dirty=False)
self.yg_glyph._yaml_add_parents(self.yg_glyph.current_block)
self.yg_glyph._yaml_supply_refs(self.yg_glyph.current_block)
#self.yg_glyph._hints_changed(self.yg_glyph.hints, dirty=False)
self.yg_glyph.sig_hints_changed.emit(self.yg_glyph.hints)
self.yg_glyph.send_yaml_to_editor()
self.top_window.set_window_title()
self.top_window.check_axis_button()

def undo(self) -> None:
self.top_window.current_axis = self.yg_glyph.axis = self.original_axis
self.yg_glyph._yaml_add_parents(self.yg_glyph.current_block)
self.yg_glyph._yaml_supply_refs(self.yg_glyph.current_block)
self.yg_glyph.sig_hints_changed.emit(self.yg_glyph.hints)
self.yg_glyph.send_yaml_to_editor()
self.top_window.set_window_title()
Expand Down Expand Up @@ -2770,10 +2775,11 @@ def _make_point_list(self) -> list:
#

def switch_to_axis(self, new_axis: str) -> None:
if self.axis == "y":
new_axis = "x"
else:
new_axis = "y"
new_axis = "x" if self.axis == "y" else "y"
#if self.axis == "y":
# new_axis = "x"
#else:
# new_axis = "y"
self.undo_stack.push(switchAxisCommand(self, self.preferences, new_axis))

#
Expand Down
55 changes: 52 additions & 3 deletions src/ygt/ygPreview.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
RENDER_GRAYSCALE,
RENDER_LCD_1,
RENDER_LCD_2,
RENDER_MONO,
#adjust_gamma,
)
from PyQt6.QtWidgets import (
Expand Down Expand Up @@ -196,7 +197,7 @@ def _build_glyph(self) -> bool:

self.pixel_size = self.max_pixel_size
char_width = ft_width
if self.render_mode != RENDER_GRAYSCALE:
if not self.render_mode in [RENDER_GRAYSCALE, RENDER_MONO]:
char_width = ft_width / 3
preview_display_width = self.width() - (PREVIEW_HORI_MARGIN * 2)
if char_width * self.pixel_size > preview_display_width:
Expand Down Expand Up @@ -258,10 +259,16 @@ def render2(self) -> None:
def render3(self) -> None:
self.set_render_mode(RENDER_LCD_2)

@pyqtSlot()
def render4(self) -> None:
self.set_render_mode(RENDER_MONO)

def set_render_mode(self, m: int) -> None:
self.render_mode = m
if self.render_mode == RENDER_GRAYSCALE:
self.make_pixmap = self.make_pixmap_grayscale
elif self.render_mode == RENDER_MONO:
self.make_pixmap = self.make_pixmap_mono
elif self.render_mode == RENDER_LCD_1:
self.make_pixmap = self.make_pixmap_lcd1
else:
Expand Down Expand Up @@ -379,7 +386,7 @@ def draw_grid(self, painter: QPainter) -> None:
pen = painter.pen()
pen.setWidth(1)
line_length = self.face.glyph_slot.bitmap.width * self.pixel_size
if self.render_mode != RENDER_GRAYSCALE:
if not self.render_mode in [RENDER_GRAYSCALE, RENDER_MONO]:
line_length = int(line_length / 3)

dark_theme = (self.theme_choice == "dark")
Expand All @@ -398,7 +405,7 @@ def draw_grid(self, painter: QPainter) -> None:
painter.drawLine(QLine(left, top, left + line_length, top))
top += self.pixel_size

if self.render_mode in [RENDER_GRAYSCALE, RENDER_LCD_1]:
if self.render_mode in [RENDER_GRAYSCALE, RENDER_LCD_1, RENDER_MONO]:
grid_width = self.face.glyph_slot.bitmap.width + 1
if self.render_mode == RENDER_LCD_1:
grid_width = round(grid_width / 3) + 1
Expand All @@ -414,6 +421,48 @@ def draw_grid(self, painter: QPainter) -> None:
painter.drawLine(QLine(left, y_top, left, y_bot))
left += self.pixel_size

def make_pixmap_mono(self) -> None:
"""Paint monochrome glyph."""
dark_theme = (self.theme_choice == "dark")
if self.theme_choice == "auto":
dark_theme = self.dark_theme

if self.pixmap == None:
self.pixmap = QPixmap(self.width(), self.height())
self.pixmap.fill(self.background_color)
painter = QPainter(self.pixmap)

if not self._build_glyph():
painter.end()
return
if len(self.Z) == 0:
painter.end()
return

xposition = self.horizontal_margin
yposition = self.vertical_margin + (self.top_char_margin * self.pixel_size)

ft_width = self.face.glyph_slot.bitmap.width
ft_rows = self.face.glyph_slot.bitmap.rows

pixcolor = QColor("white") if dark_theme else QColor("black")
bytestepper = 0
for r in range(ft_rows):
for w in range(ft_width):
if self.Z[bytestepper]:
qr = QRect(xposition, yposition, self.pixel_size, self.pixel_size)
painter.fillRect(qr, pixcolor)
xposition += self.pixel_size
bytestepper += 1
yposition += self.pixel_size
xposition = self.horizontal_margin
if self.show_grid:
self.draw_grid(painter)
painter.end()
self.setPixmap(self.pixmap)

self.sig_preview_paint_done.emit(None)


def make_pixmap_grayscale(self) -> None:
"""Paint grayscale glyph."""
Expand Down

0 comments on commit c4cc438

Please sign in to comment.