keycap-designer is a Python-based open-source application to design keycap printing images. DecentKeyboards accepts the artifacts as complete printing-ready data.
keycap-designer is good for designing a keycap set. A keycap set should have a consistent design across its keycaps. keycap-designer helps a lot to make a consistent design.
So the learning curve may be too steep if you need just one or two keycaps. In that case, please send me the printing image file, without struggling keycap-designer.
keycap-designer requires some knowledge of Python. It makes an artifact from a Python script that you write.
Look at content/tutorial_1.py to content/tutorial_3.py and content/tutorial_junana.py.
keycap-designer assumes that all keycaps are white. No colored keycaps. (But you can make alike ones with Junana profile keycaps.)
-
Windows PC or Mac
- You can manage to do with other OSes. A PDF viewer is required.
-
Python 3.11 - 3.14
-
Windows: Download from python.org
-
Mac: I recommend Homebrew
-
keycap-designer supports XDA and Junana profiles.
The name "XDA" is not very precise. In this app, "XDA" is 9.5 mm height. Some shops say 9.3 or 9.2 mm. They are identical.
keycap-designer supports:
- 1u
- 1.25u
- 1.5u
- 1.75u
- 2u
- 2.25u
- 2.75u
And can do front-side printing with 1u.
Junana is a brand new profile from DecentKeyboards. MX stem variation keycap is Junana MX. No other variations yet.
- Designed for 17 mm key pitch, but can do with 19 mm
- Junana MX is compatible with MX, Kailh Choc v2, and Gateron Low Profile switches
- Both convex and concave are available for all sizes
- Can print seamlessly on the all sides
The size variations are:
- 1u
- 1.5u
- 2.25u
CAUTION: The unit of Junana is 17 mm. The 2.25u is almost equivalent to 2u of 19 mm pitch.
More details: DecentKeyboards
- Install Python and VSCode.
- In VSCode, install Python extension.
- Make a working folder of keycap-designer. You will manage the folder with git.
- Open the folder with VSCode.
- Shift+Ctrl+P, choose "Python: Create Environment...", create a new venv or something, and activate it.
- Open a new terminal and run
pip install keycap-designer. - Run
keycap-designerin the terminal. Initialize the folder. You will findcontent,font,layout, and.vscodedirectories are made. - Exit the app.
- Open
content/tutorial_1.pyin VSCode and look at it.
from keycap_designer.manuscript import *
def generate():
TEST_IMAGE = here() / 'test_image.png'
# here() returns a pathlib.Path object representing the folder which contains the caller's source file.
# In other words, 'test_image.png' should be in the same folder with this file.
m_mandatory = Profile('XDA') @ Specifier('1u')
# Profile and Specifier objects are mandatory Descriptor objects.
# By combining Descriptor objects with @ operator, a Manuscript object is made.
# About Specifier:
# 1.5 unit (Tab key) is '15u', no period between 1 and 5.
# Bump key is 'Homing 1u'.
m_image = m_mandatory @ TopImage(TEST_IMAGE)
# TopImage() returns a Descriptor object which represents a printing image and the printing side.
# You can combine a Manuscript object with a Descriptor object by @ operator and make a Manuscript object.
red = sRGBColor('#FF0000')
green = sRGBColor('#00FF00')
blue = sRGBColor('#0000FF')
s_center_red = Style(
size=4.5,
# font size. The unit is mm.
x_loc=0.,
y_loc=0.,
# [xy]_loc are the distance from the origin. The origin is specified by [hv]_o and align.
# The unit is mm.
font=APP_FONT_DIR / 'OpenSans-VariableFont_wdth,wght.ttf',
# APP_FONT_DIR is ./font folder.
# You can use OS_FONT_DIR too.
# You can use variable fonts. The 'OpenSans-VariableFont_wdth,wght.ttf' is also a variable font.
h_o=Center,
align=Center,
v_o=Center,
# Layout to center of the printable area.
# h denotes 'horizontal', v denotes 'vertical', and o denotes 'origin'.
color=red,
# Legend's color.
side=TopSide
)
# Style object is required for Legend object.
s_left_blue = s_center_red.mod(h_o=Left, align=Left, v_o=Top, color=blue)
# You can modify Style object by mod().
# Descriptor / Manuscript objects are immutable. Use returned objects.
s_front_green = s_center_red.mod(side=FrontSide, color=green)
# Look at 'side=FrontSide'. Front-side printing is available on 1u.
legend = Legend({s_center_red: 'Red', s_left_blue: 'Blue', s_front_green: 'Green'})
# You can combine multiple legends, of course.
m_legend = m_mandatory @ legend
# Legends on white background.
m_image_legend = m_image @ legend
# Legends on image.
return [m_image, m_legend, m_image_legend] # The app requires Manuscript object list.
CONTENT = generate() # CONTENT will be imported from the app.The script above generates the preview image below:
Gray area around top and front-side area is margin area. The area will be trimmed while printing. The margin area also indicates the edge tolerance of printing. In the script and the preview above, nothing is placed on the margin area. The image and legends are cropped.
Look at the colors of legends. They look much paler than red:
,
green:
, and blue:
.
This represents the limit of printable colors. The saturation limit of printable colors are much paler than screen colors.
The saturation of those colors are truncated to printable colors. The preview image simulates printed colors.
Yes, keycap-designer implements ICC color management.
If you need the most saturated printable colors, use DeviceRGBColor with Relative instead of sRGBColor like shown in tutorial_2.py.
For images, use Relative, RelativeNoBpc, or Saturation like shown in tutorial_3.py.
The default is Perceptual.
Printed keycaps are:
from keycap_designer.manuscript import *
def generate():
'''
DecentKeyboard custom printing can do much for Junana.
Let's see the difference.
Unlike XDA, I can print all five sides of keycaps.
'''
ms: list[Manuscript] = []
m = Profile('Junana') @ Specifier('1u')
style = Style(2, 1, 1., APP_FONT_DIR / 'OpenSans-VariableFont_wdth,wght.ttf')
front = style.mod(x_loc=1.5, y_loc=1.5, side=FrontSide)
pale_red = sRGBColor('#FFB4B4')
pale_blue = sRGBColor('#B4B4FF')
ms.append(m @ Legend({style: 'BC'}) @ BackgroundColor(pale_red))
# BackgroundColor object affects all five sides.
ms.append(m @ Legend({style: 'Top SC'}) @ SideColor({TopSide: pale_red}))
# If you just fill top-side, use SideColor object.
ms.append(m @ Legend({front: 'Front SC'}) @ SideColor({FrontSide: pale_blue}))
# SideColor object of front-side becomes like this.
ms.append(m @ Legend({style: 'Top SC', front: 'BC'}) @ SideColor({TopSide: pale_blue}) @ BackgroundColor(pale_red))
# You can combine BackgroundColor and SideColor objects.
# You can see front-side legends are a bit deformed. It is adjusted to look good
# when it is printed to actual keycaps (deformation correction).
# Good but not perfect, sorry.
ms.append(m @ Wallpaper(here() / 'test_pattern.png'))
# Wallpaper covers all five sides.
# No deformation correction in this case.
ms.append(m @ Wallpaper(here() / 'test_image.png'))
# You can print one image file on all five sides seamlessly.
# No deformation correction.
return ms
CONTENT = generate()The script above generates the preview image below:
In this case, you can see that image is placed on margin area. They will be printed actually.
Look at "Front SC" area. There is margin area at the bottom of "Front SC" area, and nothing is placed on there. How does it become?
Look at "Front SC" legend. It is deformed and shortened in the vertical direction. This is deformation correction. It becomes square when it is printed.
Printed keycaps are:
The margin area at the bottom of "Front SC" area remains unprinted.
Look at the lattice pattern keycap. The left side is quite deformed. You should be careful of the deformation if you need all-sided printing.
Send me, DecentKeyboards, the preview PDF file. It contains complete printing-ready data as an attachment file.
MIT License.
You can inject your keycap_designer.profile.Profile object to keycap_designer.profile.PROFILES.
Mimic keycap_designer.profile.xda.
Do you need deform correction like Junana profile? It is quite hard. Good luck.
keycap_designer/_version.py will be generated by python -m build --wheel.
pip install pdf2image.
Download poppler-windows latest binary release,
extract poppler-* to ./tests folder, and rename the poppler-* to poppler.



